dotNET Core WebAPI 统一处理(返回值、参数验证、异常)
現在 Web 開發比較流行前后端分離
現在 Web 開發比較流行前后端分離,我們的產品也是一樣,前端使用Vue,后端使用 dotNet Core WebAPI ,在寫 API 的過程中有很多地方需要統一處理
文檔
參數驗證
返回值
異常處理
本文就說說 API 的統一處理這些事。
環境
dotNet Core:2.1
文檔
Swagger 是一個 API 文檔生成框架,在非 Core 時代就一直在使用,現在前后端分離的模式下,API 文檔更是非常重要,讓前端開發人員和后端開發人員能更好的溝通和合作,前端開發人員在 Swagger 可以了解到接口的地址、入參、出參,還能模擬調用,非常方便。
安裝
在 VS For Mac 中創建 API 項目 DotNetCoreApiSample ,在依賴項中的 NuGet 上點擊右鍵,選擇添加包,如下圖:
搜索 Swashbuckle.AspNetCore,選中搜索結果的第一條,點擊「添加包」按鈕進行添加。
配置
Startup 類的 ConfigureServices 方法中添加
services.AddSwaggerGen(options?=> {options.SwaggerDoc("v1",?new?Swashbuckle.AspNetCore.Swagger.Info{Version?=?"v1",Title?=?"DotNet?Core?WebAPI文檔"});});Startup 類的 Configure 方法中添加
app.UseSwagger(); app.UseSwaggerUI(c?=> {c.SwaggerEndpoint("/swagger/v1/swagger.json",?"DotNet?Core?WebAPI文檔"); });運行效果
運行 WepAPI 項目,在瀏覽器中輸入 http://localhost:5000/swagger ,效果如下
參數驗證
此處所說的參數驗證指的是實體類型的參數驗證,通過在實體的屬性上添加特性的方式來實現。
簡單實現
創建名為 ValidationDemoController 的 API 類,代碼如下:
using?System; using?System.Collections.Generic; using?System.ComponentModel.DataAnnotations; using?System.Linq; using?System.Threading.Tasks; using?Microsoft.AspNetCore.Mvc;namespace?DotNetCoreApiSample.Controllers {[Route("api/[controller]")]public?class?ValidationDemoController?:?Controller{[HttpPost]public?IActionResult?AddUser([FromBody]User?user){string?errorMessage?=?string.Empty;if?(!ModelState.IsValid){foreach?(var?item?in?ModelState.Values){foreach?(var?error?in?item.Errors){errorMessage?+=?error.ErrorMessage?+?"|";}}}if(!string.IsNullOrEmpty(errorMessage)){return?BadRequest(errorMessage);}return?Ok();}}public?class?User{[Required(ErrorMessage?=?"用戶Code不能為空")]public?string?Code?{?get;?set;?}[Required(ErrorMessage?=?"用戶名稱不能為空")]public?string?Name?{?get;?set;?}[Required(ErrorMessage?=?"用戶年齡不能為空")][Range(1,?100,?ErrorMessage?=?"年齡必須介于1~100之間")]public?int?Age?{?get;?set;?}public?string?Address?{?get;?set;?}} }實體類屬性使用 Required 等特性需要引用命名空間System.ComponentModel.DataAnnotations
除了上面的 Required 和 Range 標記,還有很多實用的標記,詳細參考:https://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations(v=vs.110).aspx
上面的示例代碼將錯誤信息的收集寫在了接口方法中,這是一個很不好的做法,僅僅實現了功能,下面將通過過濾器的方式來進行重構,統一處理錯誤信息
重構
添加名為 ValidateModelAttribute 的過濾器類,繼承 ActionFilterAttribute ,代碼如下
namespace?DotNetCoreApiSample.Filters {public?class?ValidateModelAttribute?:?ActionFilterAttribute{public?override?void?OnActionExecuting(ActionExecutingContext?context){if?(!context.ModelState.IsValid){var?result?=?context.ModelState.Keys.SelectMany(key?=>?context.ModelState[key].Errors.Select(x?=>?new?ValidationError(key,?x.ErrorMessage))).ToList();context.Result?=?new?ObjectResult(result);}}}public?class?ValidationError{[JsonProperty(NullValueHandling?=?NullValueHandling.Ignore)]public?string?Field?{?get;?}public?string?Message?{?get;?}public?ValidationError(string?field,?string?message){Field?=?field?!=?string.Empty???field?:?null;Message?=?message;}} }Startup 類的 ConfigureServices 方法中添加下面代碼:
services.AddMvc(options?=> {options.Filters.Add<ValidateModelAttribute>(); });使用 Postman 調用結果如下
返回值
返回值的統一處理需要下面幾個步驟:
創建統一返回結果的實體類,所有的接口方法都返回固定格式,方便前端統一處理
創建過濾器,過濾器用來攔截請求,包裝結果,統一輸出
Startup 類中進行配置注冊
結果實體類
接口的返回值需要統一的格式,下面的屬性字段是我認為必須要有的
Result:返回的結果
Message:出現錯誤或需要提示時的提示文本內容
Code:調用成功、失敗或出錯時的編碼
ReturnStatus:用來判斷接口調用狀態的
創建返回結果的實體類 BaseResultModel
public?class?BaseResultModel {public?BaseResultModel(int??code?=?null,?string?message?=?null,object?result?=?null,?ReturnStatus?returnStatus?=?ReturnStatus.Success){this.Code?=?code;this.Result?=?result;this.Message?=?message;this.ReturnStatus?=?returnStatus;}public?int??Code?{?get;?set;?}public?string?Message?{?get;?set;?}public?object?Result?{?get;?set;?}public?ReturnStatus?ReturnStatus?{?get;?set;?} } public?enum?ReturnStatus {Success?=?1,Fail?=?0,ConfirmIsContinue?=?2,Error?=?3 }過濾器類
創建名稱為 ApiResultFilterAttribute 的過濾器類,該類繼承 ActionFilterAttribute ,具體代碼如下
public?class?ApiResultFilterAttribute?:?ActionFilterAttribute {public?override?void?OnActionExecuting(ActionExecutingContext?context){base.OnActionExecuting(context);}public?override?void?OnResultExecuting(ResultExecutingContext?context){var?objectResult?=?context.Result?as?ObjectResult;context.Result?=?new?OkObjectResult(new?BaseResultModel(code:200,?result:?objectResult.Value));} }在過濾器中將接口的返回值獲取后重新包裝到 BaseResultModel 模型類中進行返回。
Startup 配置
在 Startup 類的 ConfigureServices 方法中添加如下代碼
services.AddMvc(options?=> {options.Filters.Add<ValidateModelAttribute>();options.Filters.Add<ApiResultFilterAttribute>(); });添加示例接口方法
[HttpGet] public?IActionResult?GetUserCode() {return?Ok("oec2003"); }運行效果
使用 Postman 調用該接口方法,返回結果如下
繼續重構參數驗證
添加了返回值的過濾器類后,調用之前的參數驗證的接口,會發現返回結果如下
{"code":?200,"message":?null,"result":?[{"field":?"Age","message":?"年齡必須介于1~100之間"}],"returnStatus":?1 }接口會調用兩次過濾器,先調用參數驗證的過濾器,再調用返回值的過濾器,導致驗證失敗的接口返回值狀態也是成功的,所以需要做進一步重構。
1、添加 ValidationFailedResultModel 類
public?class?ValidationFailedResultModel?:?BaseResultModel {public?ValidationFailedResultModel(ModelStateDictionary?modelState){Code?=?422;Message?=?"參數不合法";Result?=?modelState.Keys.SelectMany(key?=>?modelState[key].Errors.Select(x?=>?new?ValidationError(key,?x.ErrorMessage))).ToList();ReturnStatus?=?ReturnStatus.Fail;} }public?class?ValidationError {[JsonProperty(NullValueHandling?=?NullValueHandling.Ignore)]public?string?Field?{?get;?}public?string?Message?{?get;?}public?ValidationError(string?field,?string?message){Field?=?field?!=?string.Empty???field?:?null;Message?=?message;} }將錯誤信息的收集移到了 ValidationFailedResultModel 類中,所以
2、修改 ValidateModelAttribute 過濾器,在修改代碼之前,先要添加名為 ValidationFailedResult 的類,該類繼承 ObjectResult ,用做參數驗證的結果收集。
public?class?ValidationFailedResult:?ObjectResult {public?ValidationFailedResult(ModelStateDictionary?modelState):?base(new?ValidationFailedResultModel(modelState)){StatusCode?=?StatusCodes.Status422UnprocessableEntity;} }修改 ValidateModelAttribute 類
public?override?void?OnActionExecuting(ActionExecutingContext?context) {if?(!context.ModelState.IsValid){context.Result?=?new?ValidationFailedResult(context.ModelState);} }3、修改 ApiResultFilterAttribute 過濾器,添加對 ValidationFailedResult 類型的判斷
public?override?void?OnResultExecuting(ResultExecutingContext?context) {if?(context.Result?is?ValidationFailedResult){var?objectResult?=?context.Result?as?ObjectResult;context.Result?=?objectResult;}else{var?objectResult?=?context.Result?as?ObjectResult;context.Result?=?new?OkObjectResult(new?BaseResultModel(code:?200,?result:?objectResult.Value));} }4、調用參數驗證接口結果如下
異常處理
異常處理和參數驗證的方式基本相同,有以下幾個步驟
1、創建名為 CustomExceptionResultModel 的模型類
public?class?CustomExceptionResultModel:BaseResultModel {public?CustomExceptionResultModel(int??code,?Exception?exception){Code?=?code;Message?=?exception.InnerException?!=?null??exception.InnerException.Message?:exception.Message;Result?=?exception.Message;ReturnStatus?=?ReturnStatus.Error;} }2、創建名為 CustomExceptionResult 的異常結果類
public?class?CustomExceptionResult:ObjectResult {public?CustomExceptionResult(int??code,?Exception?exception):?base(new?CustomExceptionResultModel(code,?exception)){StatusCode?=?code;} }3、創建名為 CustomExceptionAttribute 的異常過濾器類,繼承自 IExceptionFilter
public?class?CustomExceptionAttribute?:?IExceptionFilter {public?void?OnException(ExceptionContext?context){HttpStatusCode?status?=?HttpStatusCode.InternalServerError;//處理各種異常context.ExceptionHandled?=?true;context.Result?=?new?CustomExceptionResult((int)status,?context.Exception);} }4、Startup 配置
在 Startup 類的 ConfigureServices 方法中添加如下代碼
services.AddMvc(options?=> {options.Filters.Add<ValidateModelAttribute>();options.Filters.Add<ApiResultFilterAttribute>();options.Filters.Add<CustomExceptionAttribute>(); });感興趣的朋友可以在 Github 上下載示例代碼進行調試。
總結
如果是從零開始搭建一個 WebAPI 項目,這些基礎處理是必不可少的,有了這些做保障才能專注于業務代碼的編寫。
本文只是拋磚引玉,同樣的思路我們還可以實現更多的功能,例如
如果某些特殊接口需要直接返回值怎么辦?
怎樣記錄耗時較長的接口?
怎樣做接口的驗證?
點擊「閱讀原文」可訪問示例代碼。
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的dotNET Core WebAPI 统一处理(返回值、参数验证、异常)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用 Azure DevTest Lab
- 下一篇: .net测试篇之单元测试/集成测试神器A