dotNET:怎样处理程序中的异常(实战篇)?
在上篇 《dotNET:怎樣處理程序中的異常(理論篇)》 中講了一些程序中出現(xiàn)異常怎樣處理的理論知識(shí),本文將以代碼的方式來(lái)進(jìn)行實(shí)踐。
環(huán)境
dotNET Core:3.1
工具:Rider 2019.3.2
系統(tǒng):macOS 10.15.4
創(chuàng)建項(xiàng)目
在 Rider 中創(chuàng)建示例項(xiàng)目 ExceptionDemo ,該項(xiàng)目為 dotNET Core 3.1 的 WebAPI 項(xiàng)目,為了演示方便,不同層級(jí)以目錄的方式放在了一個(gè)項(xiàng)目中,創(chuàng)建好的項(xiàng)目目錄結(jié)構(gòu)如下:
Controllers
UserController:操作用戶(hù)的控制器
CustomExceptions
UserNotFoundException:用戶(hù)不存在的自定義異常類(lèi)
Filters
CustomerExceptionAttribute:異常結(jié)果處理過(guò)濾器
ResultFilterAttribute:普通結(jié)果處理過(guò)濾器
Models
CustomExceptionResult:異常返回的處理類(lèi)
CustomExceptionResultModel:異常內(nèi)容的模型類(lèi)
DataResult:普通結(jié)果的返回處理類(lèi)
DataResultModel:普通結(jié)果的內(nèi)容模型類(lèi)
MessageResult:消息結(jié)果的返回處理類(lèi)
MessageResultModel:消息結(jié)果的內(nèi)容模型類(lèi)
ResultModelBase:返回結(jié)果內(nèi)容模型的基類(lèi)
User:示例中用戶(hù)的實(shí)體類(lèi)
Repositories
IUserRepository:用戶(hù)操作數(shù)據(jù)庫(kù)的接口
UserRepository:用戶(hù)操作數(shù)據(jù)庫(kù)的實(shí)現(xiàn)類(lèi)
Services
IUserService:用戶(hù)業(yè)務(wù)層的接口
UserService:用戶(hù)業(yè)務(wù)層的實(shí)現(xiàn)類(lèi)
結(jié)果的返回
接口的返回可以歸納為三種情況:
正常的請(qǐng)求數(shù)據(jù)的返回
通過(guò)判斷需要返回一些消息給前端進(jìn)行提示
異常的返回
所以上面定義了 DataResult、MessageResult 和 CustomExceptionResult 相關(guān)類(lèi)來(lái)進(jìn)行這三種情況的封裝。
這三個(gè)類(lèi)都繼承 ResultModelBase 類(lèi),ResultModelBase 類(lèi)中只定義了 Code
public?class?ResultModelBase {public?int??Code?{?get;?set;?} }DataResultModel 類(lèi)用屬性 Data 來(lái)包裝返回結(jié)果
public?class?DataResultModel:ResultModelBase {public?DataResultModel(object?data,int??code?=?200){Code?=?code;Data?=?data;}public?object?Data?{?get;?set;?} }MessageResultModel 類(lèi)使用屬性 Message 類(lèi)返回消息文本
public?class?MessageResultModel:ResultModelBase {public?MessageResultModel(string?massage,int??code?=?200){Code?=?code;Message?=?massage;}public?string?Message?{?get;?set;?} }CustomExceptionResultModel 類(lèi)中可以傳入 Exception 類(lèi)型和定義一些其他的相關(guān)屬性
public?class?CustomExceptionResultModel:ResultModelBase {public?CustomExceptionResultModel(Exception?exception,int??code?=?500){Code?=?code;Reason?=?exception.InnerException?!=?null??exception.InnerException.Message?:exception.Message;}public?string?Reason?{?get;?set;?} }DataResult、MessageResult 和 CustomExceptionResult 類(lèi)都是繼承自O(shè)bjectResult,將相對(duì)應(yīng)的 Model 類(lèi)包裝后通過(guò)構(gòu)造函數(shù)賦值給 ObjectResult 的 Value 屬性,用于最后的結(jié)果返回。
public?class?DataResult:?ObjectResult {public?DataResult(object?data?,?int??code=200?):?base(new?DataResultModel(data,code)){StatusCode?=?200;} } public?class?MessageResult:ObjectResult {public?MessageResult(string?message,?int??code=200?):?base(new?MessageResultModel(message,code)){StatusCode?=?200;} } public?class?CustomExceptionResult:ObjectResult {public?CustomExceptionResult(Exception?exception,HttpStatusCode?statusCode,??int??code=500?):?base(new?CustomExceptionResultModel(exception,code)){StatusCode?=?(int)statusCode;} }使用兩個(gè)過(guò)濾器對(duì)返回結(jié)果進(jìn)行處理
public?class?CustomerExceptionAttribute:?IExceptionFilter {public?void?OnException(ExceptionContext?context){HttpStatusCode?status?=?HttpStatusCode.InternalServerError;int?code?=?(int)?status;//處理各種異常if?(context.Exception?is?UserNotFoundException){code?=?500001;}context.Result?=?new?CustomExceptionResult(context.Exception,status?,code);context.ExceptionHandled?=?true;} }public?class?ResultFilterAttribute:ActionFilterAttribute {public?override?void?OnResultExecuting(ResultExecutingContext?context){var?objectResult?=?context.Result?as?ObjectResult;if?(objectResult?.Value?==?null){context.Result=new?NotFoundObjectResult(new?MessageResult("未找到資源"));}if?(context.Result?is?MessageResult){context.Result?=?new?MessageResult(objectResult.Value.ToString());}else?if?(context.Result?is?OkObjectResult?||?context.Result?is?ObjectResult){context.Result?=?new?DataResult(objectResult.Value);}} }用戶(hù)添加接口
在 UserRepository 中添加 AddUser 方法
public?User?AddUser(User?user) {int?id=_users.OrderByDescending(x?=>?x.Id).First().Id?+?1;user.Id?=?id;_users.Add(user);return?user; }示例中沒(méi)有實(shí)際操作數(shù)據(jù)庫(kù),_users 是一個(gè) List對(duì)象,當(dāng) _users 為 Null 或內(nèi)容為空時(shí),_users.OrderByDescending(x => x.Id).First() 的執(zhí)行就會(huì)報(bào)錯(cuò),空對(duì)象的問(wèn)題在實(shí)際程序中無(wú)處不在,修改后的代碼如下:
public?User?AddUser(User?user) {int?id?=?1;if?(_users.Any()){id=_users.OrderByDescending(x?=>?x.Id).First().Id?+?1;}user.Id?=?id;_users.Add(user);return?user; }在 Controller 層的 AddUser 方法也需要對(duì)入?yún)?shí)體進(jìn)行檢查
[HttpPost] public?User?AddUser(User?user) {return?_userService.AddUser(user); }public?class?User {public?int?Id?{?get;?set;?}[Required(ErrorMessage?=?"用戶(hù)名不能為空")]public?string?Name?{?get;?set;?}[Required(ErrorMessage?=?"用戶(hù)編碼不能為空")]public?string?Code?{?get;?set;?} }實(shí)際情況下接口層的入?yún)?shí)體和底層的數(shù)據(jù)實(shí)體需要分開(kāi),然后使用 AutoMapper 之類(lèi)的映射工具進(jìn)行轉(zhuǎn)換,本示例中使用了同一個(gè) User 。
使用 Postman 進(jìn)行調(diào)用,當(dāng) Name 或 Code 為空時(shí),結(jié)果如下:
默認(rèn)的返回結(jié)果格式和上面定義的統(tǒng)一的格式有些區(qū)別,大家可以思考下,怎樣使用過(guò)濾器的方式將參數(shù)驗(yàn)證的返回信息進(jìn)行統(tǒng)一輸出。
根據(jù) Id 獲取用戶(hù)的名稱(chēng)
在 UserRepository 中有根據(jù) Id 獲取 User 對(duì)象的方法
public?User?GetUserById(int?id) {return?_users.Find(x?=>?x.Id?==?id); }在 UserService 中添加 GetUserName 方法獲取名稱(chēng)
public?string?GetUserName(int?id) {User?user=_userRepository.GetUserById(id);if?(user?==?null){throw?new?UserNotFoundException($"用戶(hù)id:{id}?在數(shù)據(jù)庫(kù)不存在"?);}return?user.Name; }當(dāng)通過(guò) id 找不到 User 對(duì)象時(shí),可以?huà)伋?UserNotFoundException 異常,如果只是對(duì) user 對(duì)象進(jìn)行 Null 判斷然后返回一個(gè)空字符,就弄不清楚是 user 對(duì)象不存在還是用戶(hù)名為空。
獲取用戶(hù)全名
下面用一個(gè)獲取用戶(hù)全名(包含部門(mén))的業(yè)務(wù)來(lái)模擬異常的重新包裝,部門(mén)操作的相關(guān)類(lèi)就不在贅述了,可以在文章最下方的鏈接中查看源碼。
UserController 中添加了接口方法
[HttpGet("{id}")] public?string?GetFullName(int?id) {return?_userService.GetFullName(id); }UserService 中添加 GetFullName 方法
public?string?GetFullName(int?id) {try{User?user?=?GetUserById(id);string?deptName?=?_deptService.GetDeptName(user.ParentId);//處理其他邏輯return?$"{user.Name}[{deptName}]";}catch?(Exception?e){throw?new?UserFullNameGenException($"用戶(hù)?Id?為?{id}?的?FullName?生產(chǎn)失敗",e);} }GetUserById 方法和 _deptService.GetDeptName 方法中都可能拋異常,在上次可以捕獲異常然后拋出符合當(dāng)前業(yè)務(wù)的 UserFullNameGenException 異常;
捕獲的異常 e 作為 UserFullNameGenException 異常的 InnerException 傳入,這樣如果層級(jí)比較多,通過(guò) InnerException 就可以追溯到最底層的原因。
當(dāng)輸入?yún)?shù)為用戶(hù)不存在的時(shí)候調(diào)用結(jié)果如下:
當(dāng)輸入?yún)?shù)為用戶(hù)的部門(mén)不存在時(shí)調(diào)用結(jié)果如下:
通過(guò)二次捕獲提示的錯(cuò)誤信息是跟當(dāng)前業(yè)務(wù)有關(guān)的,可以更容易定位問(wèn)題,更底一層的原因可以在 InnerException 中獲取;
兩次異常是不同原因造成的,但對(duì)于這個(gè)業(yè)務(wù)來(lái)說(shuō)就是獲取 FullName 失敗,返回的錯(cuò)誤碼也是一致的 500100 ;
因?yàn)橛辛硕尾东@,異常堆棧信息中只能定位到最上層捕獲異常的地方,如果需要知道更底層的異常堆棧,可以將 InnerException 的堆棧信息進(jìn)行合并。
最后
本文以一個(gè)簡(jiǎn)單的示例演示了代碼中異常的處理,但重要的不是編碼而是處理問(wèn)題的思路。具體應(yīng)該怎么做還是需要結(jié)合當(dāng)前的上下文。希望本文對(duì)您有所幫助。
示例源碼:https://github.com/oec2003/DotNetCoreThreeAPIDemo/tree/master/ExceptionDemo
總結(jié)
以上是生活随笔為你收集整理的dotNET:怎样处理程序中的异常(实战篇)?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 技术脱钩后软硬件磨合优化不失为一条出路
- 下一篇: 回归统计在DMP中的实战应用