在 .NET Core API中,如果想在接口中验证某个模型,首先需要在该模型(类)类的属性上打上来自于`System.ComponentModel.DataAnnotations`命名空间下的特性。 ``` public class SomeModel { [Required(ErrorMessage = "电话号码必填")] [StringLength(30, ErrorMessage = "电话号码长度不能超过30")] [RegularExpression(@"^1[3458][0-9]{9}$", ErrorMessage = "手机号格式不正确")] public string Phone { get; set; } [Required(ErrorMessage = "姓名必填")] [StringLength(10, ErrorMessage = "姓名长度不能超过10")] public string Name { get; set; } } ``` 然后在控制器方法中: ``` [HttpPost] public async Task SomeActionMethod([FromBody]SomeModel command){} ``` 此时请求`SomeActionMethod`这个接口,如果`SomeModel`的字段值不符合验证定义,就会返回如下报错: ``` { "Name": [ "姓名必填" ], "Phone": [ "手机号格式不正确" ] } ``` 而在实际项目一般都有统一的返回格式,比如: ``` { "success": true, "message": "", "errors": { "name":[], "phone":[] } } ``` **如何让API返回的模型验证失败信息符合自定义格式呢**? # 错误的方式 在以前的 ASP .NET MVC或者 .NET Core 2.0时代我们可以通过继承`ActionFilterAttribute`这个基类来实现。 ``` public class ValidateModelAttribute : ActionFiltereAttribute { public override void OnActionExecuting(ActionExecutingContext context) { if(!context.ModelState.IsValid) { context.Result = new BadRequestObjectResult(context.ModelState); } } } ``` 在`Startup.cs`中配置。 ``` services.AddMvcCore(options => { options.Filters.Add(typeof(ValidateModelAttribute)); }); ``` 最后在控制器方法上套用。 ``` [ValidateModel] [HttpPost] public async Task SomeActionMethod([FromBody]SomeModel command){} ``` **很不幸**,在 .NET Core 2.2下,虽然以上写法没有报错,但实际是走不通的。 # 正确的方式 阅读官网后发现在 .NET Core 2.2下可以通过实现`IResultFilter`接口来实现。在这个接口的`OnResultExecuting`方法中返回自定义的格式。 > 在.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。 > > 可以在一个类上增加多个过滤器。每个过滤器都有同步和异步方法,但不能同时使用同步或异步方法。如果既有同步方法也有异步方法会以异步方法优先。 言归正传。首先实现`IResultFilter`接口。 ``` /// /// 模型验证失败后返回的结果 /// public class ValidateModelResultServiceFilter : IResultFilter { public void OnResultExecuted(ResultExecutedContext context) { } public void OnResultExecuting(ResultExecutingContext context) { if (!context.ModelState.IsValid) { context.Result = new ValidationFailedResult(context.ModelState); } } } ``` 以上返回了自定义的`ValidationFailedResult` ``` public class ValidationFailedResult : ObjectResult { public ValidationFailedResult(ModelStateDictionary modelState):base(new ValidationVM(modelState)) { StatusCode = StatusCodes.Status422UnprocessableEntity; } } ``` 以上在`ValidationFailedResult`的构造函数中把`ValidationVM`实例交给了`ObjectResult`这个基类,`ObjectResult`负责把`ValidationVM`实例对象打印出来,即接口返回的格式。 ``` public class ValidationVM { /// /// 返回数据是否成功 /// public bool Success { get; set; } /// /// 返回描述 /// public string Message { get; set; } public List Errors { get; } public ValidationVM(ModelStateDictionary modelState) { Success = false; Message = "输入模型无法通过验证"; Errors = modelState.Keys.SelectMany(key => modelState[key].Errors.Select(x => new VaidationError(key, x.ErrorMessage))).ToList(); } } public class VaidationError { [JsonProperty(NullValueHandling=NullValueHandling.Ignore)] public string Field { get; } public string Message { get; } public VaidationError(string field, string message) { Field = string.IsNullOrEmpty(field) ? null : field; Message = message; } } ``` 需要在`Startup.cs`中放入DI容器。 ``` services.AddScoped(); ``` 最后放置在某个控制器方法上。 ``` [ServiceFilter(typeof(ValidateModelResultServiceFilter))] [HttpPost] public async Task SomeActionMethod([FromBody]SomeModel model){} ```