如何避免 InvalidModelStateResponseFactory 干扰已在 FluentValidation 中生成的错误响应

问题描述

我正在使用 FluentValidation 开发 .NET Core 3.0 MVC 项目,以验证来自 React 前端的输入。我不能指望输入在前端得到验证。

我正在使用 InvalidModelStateResponseFactory 来捕获转换错误,例如"zz" 为整数,并使用 FluentValidation 验证数字是否有效,例如

我的问题是,当 FluentValidation 捕获错误时,即使没有转换错误,InvalidModelStateResponseFactory 也会随后捕获它。并且 FluentValidation 的错误信息丢失了。

FluentValidation 可以验证 int 是否过大。例如:

public class XCommand
{
    public int myInt {get;set;}
}

和验证器:

public class XCommandValidator : AbstractValidator<XCommand>
{
    RuleFor(x => x.myInt).InclusiveBetween(0,99).WithMessage("only below 100");
}

如果验证器失败 {myInt : 100},我可以捕获异常并返回一个很好的错误响应。

如果前端发送 {myInt : "zz"} 我得到一个由 MVC 生成错误(不涉及 InvalidModelStateResponseFactory):

{"errors":{"myInt":["无法将字符串转换为整数:zz。路径 'myInt',第 1 行,位置 1。"]},"type":"https:/ /tools.ietf.org/html/rfc7231#section-6.5.1","title":"发生了一个或多个验证错误。","status":400,"traceId":"|ae6d5a28-4822b30b8c0ae039。"}

当我遇到转换错误时,我想控制我发送给前端的错误响应的格式。为了捕捉转换错误,我在 Startup.cs 中设置了一个 InvalidModelStateResponseFactory:

services.Configure<ApiBehaviorOptions>(options =>
{
   options.InvalidModelStateResponseFactory = contex =>
   {
      // create a nice error response
   };
}); 

我的问题是,如果我没有转换错误,而是一个无效的值,例如{myInt : 100} 并且在 FluentValidator 中失败。激活 InvalidModelStateResponseFactory 并将 InvalidModelStateResponseFactory 中生成错误响应发送到前端。 FluentValidation 中生成的消息丢失。

如何避免 InvalidModelStateResponseFactory 干扰已在 FluentValidation 中生成错误响应?

解决方法

解决方案是创建一个自定义验证过滤器,并捕获来自 MVC“本机”和 FluentValidator 的所有验证错误。

public class CustomValidationFilter : IActionFilter
{
    public void OnActionExecuted(ActionExecutedContext context) { }
    public void OnActionExecuting(ActionExecutingContext context)
    {
        if (!context.ModelState.IsValid)
        {               
            SortedList<string,List<string>> err = new SortedList<string,List<string>>();
            foreach (var modelStateKey in context.ModelState.Keys)
            {
                var modelStateVal = context.ModelState[modelStateKey];
                if (modelStateVal.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid)
                {                     
                    err.Add(modelStateKey,modelStateVal.Errors.Select(e => e.ErrorMessage).ToList());                       
                }
            }

            var responseObj = new
            {
                Errors = err
            };

            context.Result = new JsonResult(responseObj)
            {
                StatusCode = 400
            };
        }
    }
}

在 StartUp.ConfigureServices 中,必须添加:

services.Configure<ApiBehaviorOptions>(options =>
{
    options.SuppressModelStateInvalidFilter = true;
});