天天看點

dotNET Core WebAPI 統一處理(傳回值、參數驗證、異常)

現在 Web 開發比較流行前後端分離,我們的産品也是一樣,前端使用Vue,後端使用 dotNet Core WebAPI ,在寫 API 的過程中有很多地方需要統一處理:

  • 文檔
  • 參數驗證
  • 傳回值
  • 異常處理

本文就說說 API 的統一處理這些事。

環境

dotNet Core:2.1

VS For Mac:8.1

Swagger 是一個 API 文檔生成架構,在非 Core 時代就一直在使用,現在前後端分離的模式下,API 文檔更是非常重要,讓前端開發人員和後端開發人員能更好的溝通和合作,前端開發人員在 Swagger 可以了解到接口的位址、入參、出參,還能模拟調用,非常友善。

安裝

在 VS For Mac 中建立 API 項目 DotNetCoreApiSample ,在依賴項中的 NuGet 上點選右鍵,選擇添加包,如下圖:

dotNET Core WebAPI 統一處理(傳回值、參數驗證、異常)

搜尋

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

,效果如下

dotNET Core WebAPI 統一處理(傳回值、參數驗證、異常)

此處所說的參數驗證指的是實體類型的參數驗證,通過在實體的屬性上添加特性的方式來實作。

簡單實作

建立名為 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 調用結果如下

dotNET Core WebAPI 統一處理(傳回值、參數驗證、異常)

傳回值的統一處理需要下面幾個步驟:

  • 建立統一傳回結果的實體類,所有的接口方法都傳回固定格式,友善前端統一處理
  • 建立過濾器,過濾器用來攔截請求,包裝結果,統一輸出
  • 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 調用該接口方法,傳回結果如下

dotNET Core WebAPI 統一處理(傳回值、參數驗證、異常)

繼續重構參數驗證

添加了傳回值的過濾器類後,調用之前的參數驗證的接口,會發現傳回結果如下

{
  "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 類中,是以

ValidateModelAttribute 過濾器也需要調整。

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、調用參數驗證接口結果如下

dotNET Core WebAPI 統一處理(傳回值、參數驗證、異常)

異常處理和參數驗證的方式基本相同,有以下幾個步驟

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 配置

services.AddMvc(options =>
{
    options.Filters.Add<ValidateModelAttribute>();
    options.Filters.Add<ApiResultFilterAttribute>();
    options.Filters.Add<CustomExceptionAttribute>();
});
           

感興趣的朋友可以在 Github 上下載下傳示例代碼進行調試。

總結

如果是從零開始搭建一個 WebAPI 項目,這些基礎處理是必不可少的,有了這些做保障才能專注于業務代碼的編寫。

本文隻是抛磚引玉,同樣的思路我們還可以實作更多的功能,例如

  • 如果某些特殊接口需要直接傳回值怎麼辦?
  • 怎樣記錄耗時較長的接口?
  • 怎樣做接口的驗證?

點選「閱讀原文」可通路示例代碼。

dotNET Core WebAPI 統一處理(傳回值、參數驗證、異常)

微信公衆号:不止dotNET

作者: oec2003

出處: http://oec2003.cnblogs.com/

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連結,否則 保留追究法律責任的權利。

繼續閱讀