鼎鼎知识库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

17.NET Core 2.2在API中返回自定义模型验证失败信息.md 5.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. 在 .NET Core API中,如果想在接口中验证某个模型,首先需要在该模型(类)类的属性上打上来自于`System.ComponentModel.DataAnnotations`命名空间下的特性。
  2. ```
  3. public class SomeModel
  4. {
  5. [Required(ErrorMessage = "电话号码必填")]
  6. [StringLength(30, ErrorMessage = "电话号码长度不能超过30")]
  7. [RegularExpression(@"^1[3458][0-9]{9}$", ErrorMessage = "手机号格式不正确")]
  8. public string Phone { get; set; }
  9. [Required(ErrorMessage = "姓名必填")]
  10. [StringLength(10, ErrorMessage = "姓名长度不能超过10")]
  11. public string Name { get; set; }
  12. }
  13. ```
  14. 然后在控制器方法中:
  15. ```
  16. [HttpPost]
  17. public async Task<IActionResult> SomeActionMethod([FromBody]SomeModel command){}
  18. ```
  19. 此时请求`SomeActionMethod`这个接口,如果`SomeModel`的字段值不符合验证定义,就会返回如下报错:
  20. ```
  21. {
  22. "Name": [
  23. "姓名必填"
  24. ],
  25. "Phone": [
  26. "手机号格式不正确"
  27. ]
  28. }
  29. ```
  30. 而在实际项目一般都有统一的返回格式,比如:
  31. ```
  32. {
  33. "success": true,
  34. "message": "",
  35. "errors": {
  36. "name":[],
  37. "phone":[]
  38. }
  39. }
  40. ```
  41. **如何让API返回的模型验证失败信息符合自定义格式呢**?
  42. # 错误的方式
  43. 在以前的 ASP .NET MVC或者 .NET Core 2.0时代我们可以通过继承`ActionFilterAttribute`这个基类来实现。
  44. ```
  45. public class ValidateModelAttribute : ActionFiltereAttribute
  46. {
  47. public override void OnActionExecuting(ActionExecutingContext context)
  48. {
  49. if(!context.ModelState.IsValid)
  50. {
  51. context.Result = new BadRequestObjectResult(context.ModelState);
  52. }
  53. }
  54. }
  55. ```
  56. 在`Startup.cs`中配置。
  57. ```
  58. services.AddMvcCore(options => {
  59. options.Filters.Add(typeof(ValidateModelAttribute));
  60. });
  61. ```
  62. 最后在控制器方法上套用。
  63. ```
  64. [ValidateModel]
  65. [HttpPost]
  66. public async Task<IActionResult> SomeActionMethod([FromBody]SomeModel command){}
  67. ```
  68. **很不幸**,在 .NET Core 2.2下,虽然以上写法没有报错,但实际是走不通的。
  69. # 正确的方式
  70. 阅读官网后发现在 .NET Core 2.2下可以通过实现`IResultFilter`接口来实现。在这个接口的`OnResultExecuting`方法中返回自定义的格式。
  71. > 在.NET Core 2.2 API中,当请求过来会来到请求管道,经过的路径大致包括:other middlewares → routing middleware → action selection → filter middlewares, 而在filter middleware中所经历的中间件依次是:authorization filter → resource filter → exception filter → model binding机制 → action filter → result filter,而上面说的`IResultFilter`就属于result filter。
  72. >
  73. > 可以在一个类上增加多个过滤器。每个过滤器都有同步和异步方法,但不能同时使用同步或异步方法。如果既有同步方法也有异步方法会以异步方法优先。
  74. 言归正传。首先实现`IResultFilter`接口。
  75. ```
  76. /// <summary>
  77. /// 模型验证失败后返回的结果
  78. /// </summary>
  79. public class ValidateModelResultServiceFilter : IResultFilter
  80. {
  81. public void OnResultExecuted(ResultExecutedContext context)
  82. {
  83. }
  84. public void OnResultExecuting(ResultExecutingContext context)
  85. {
  86. if (!context.ModelState.IsValid)
  87. {
  88. context.Result = new ValidationFailedResult(context.ModelState);
  89. }
  90. }
  91. }
  92. ```
  93. 以上返回了自定义的`ValidationFailedResult`
  94. ```
  95. public class ValidationFailedResult : ObjectResult
  96. {
  97. public ValidationFailedResult(ModelStateDictionary modelState):base(new ValidationVM(modelState))
  98. {
  99. StatusCode = StatusCodes.Status422UnprocessableEntity;
  100. }
  101. }
  102. ```
  103. 以上在`ValidationFailedResult`的构造函数中把`ValidationVM`实例交给了`ObjectResult`这个基类,`ObjectResult`负责把`ValidationVM`实例对象打印出来,即接口返回的格式。
  104. ```
  105. public class ValidationVM
  106. {
  107. /// <summary>
  108. /// 返回数据是否成功
  109. /// </summary>
  110. public bool Success { get; set; }
  111. /// <summary>
  112. /// 返回描述
  113. /// </summary>
  114. public string Message { get; set; }
  115. public List<VaidationError> Errors { get; }
  116. public ValidationVM(ModelStateDictionary modelState)
  117. {
  118. Success = false;
  119. Message = "输入模型无法通过验证";
  120. Errors = modelState.Keys.SelectMany(key => modelState[key].Errors.Select(x => new VaidationError(key, x.ErrorMessage))).ToList();
  121. }
  122. }
  123. public class VaidationError
  124. {
  125. [JsonProperty(NullValueHandling=NullValueHandling.Ignore)]
  126. public string Field { get; }
  127. public string Message { get; }
  128. public VaidationError(string field, string message)
  129. {
  130. Field = string.IsNullOrEmpty(field) ? null : field;
  131. Message = message;
  132. }
  133. }
  134. ```
  135. 需要在`Startup.cs`中放入DI容器。
  136. ```
  137. services.AddScoped<ValidateModelResultServiceFilter>();
  138. ```
  139. 最后放置在某个控制器方法上。
  140. ```
  141. [ServiceFilter(typeof(ValidateModelResultServiceFilter))]
  142. [HttpPost]
  143. public async Task<IActionResult> SomeActionMethod([FromBody]SomeModel model){}
  144. ```