ASP.NET 核心中的流利验证

Fluent Validation in ASP.NET Core

我试图将我之前在 Asp.net MVC 4 上的项目中的验证替换为 Asp.net Core。并且有一些问题。 Asp.net 核心项目中的流程是这样的:

中间件 => ControllerCTOR => FluValidator => 过滤器 => 动作

此外,当 FluValidator 中的某些规则失败时,它只是 return 通过中间件堆栈向客户端响应错误。但我需要在 Filter 或 Action 中访问 ModelState。

为什么这不能正常工作?或者,如果它真的是正确的流程,如何让它更深入到行动?

启动

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc(options =>
                        {
                            options.Filters.Add(typeof(ValidateModelAttribute));
                        })
            .SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
            .AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<Startup>());

    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddNLog();
        env.ConfigureNLog("nlog.config");

        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseHsts();
        }

        app.UseHttpsRedirection();

        // Enable middleware to serve generated Swagger as a JSON endpoint.
        app.UseSwagger();

        // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), 
        // specifying the Swagger JSON endpoint.
        app.UseSwaggerUI(c =>
        {
            c.SwaggerEndpoint("/swagger/v1/swagger.json", "CorpLight API V1");
        });

        app.UseMiddleware<RequestResponseLoggingMiddleware>();
        app.UseMiddleware<ErrorHandlingMiddleware>();
        app.UseMiddleware<AuthenticateMiddleware>();

        app.UseMvc();
    }

中间件

    private readonly RequestDelegate _next;

    public ErrorHandlingMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            await HandleExceptionAsync(context, ex);
        }
    }

验证者

public class CardInformationRequestValidator : AbstractValidator<RequestModel<CardInformationRequest>>
{
    public CardInformationRequestValidator()
    {
        RuleFor(x => x.Request.RU)
            .NotNull()
            .NotEmpty();

        RuleFor(x => x.Request.Currency)
            .NotNull()
            .NotEmpty();

        RuleFor(x => x.Request.AccountNumber)
            .NotNull()
            .NotEmpty();
    }
}

控制器

[Route("api/[controller]")]
[ApiController]
public class CardController : ControllerBase
{
    private readonly ICardRepo _cardRepo;
    private readonly IMapper _mapper;

    public CardController(ICardRepo cardRepo, IMapper mapper)
    {
        _cardRepo = cardRepo;
        _mapper = mapper;
    }

    [HttpPost]
    public async Task<MessageWithElements<CardInformation, CardInfo>> CardInformations(RequestModel<CardInformationRequest> request)
    {
        if (!ModelState.IsValid)
            throw new InvalidParametersException($"can't be empty");

         //logic

    }
}

过滤器

public class ValidateModelAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (!context.ModelState.IsValid)
        {
            //logic
        }
    }
}

典型有效Json:

{ 
  "request": {
    "ru": "string",
    "accountNumber": "string",
    "currency": 1
  }
}

典型无效Json:

{ 
  "request": {
    "ru": "string",
    "accountNumber": "string",
    "currency": 0
  }
}

当货币不为零时有效,并到达过滤器。但是当它为零时,NotEmpty 失败并返回。

有效请求的典型响应:

{
  "elements": [
    {
      <object fields>
    }
  ],
  "messageText": "string",
  "messageNumber": 1
}

无效请求(400 错误请求)的典型响应:

{
  "Request.Currency": [
    "'Request. Currency' must not be empty."
  ]
} 

即使模型无效,执行流程实际上也会到达 ValidateModelAttribute 和操作。但是有一个特定的情况,当 Request 属性 是 null 并且 CardInformationRequestValidator 在验证期间抛出异常。例如,当验证器尝试检查此规则时

RuleFor(x => x.Request.RU)
    .NotNull()
    .NotEmpty();

它尝试获取 RU 属性 值但它抛出 NullReferenceException 因为 x.Requestnull。所以解决方案是更新验证逻辑

public CardInformationRequestValidator()
{
    RuleFor(x => x.Request)
        .NotNull()
        .DependentRules(() =>
        {
            RuleFor(x => x.Request.RU)
                .NotNull()
                .NotEmpty();

            RuleFor(x => x.Request.Currency)
                .NotNull()
                .NotEmpty();

            RuleFor(x => x.Request.AccountNumber)
                .NotNull()
                .NotEmpty();
        });
}

阅读更多in docs and on github

我找到了解决办法。问题出在过滤器中。由于 OnActionExecuting 方法请求永远不会到达。验证后,如果有任何失败的规则上下文直接进入 OnResultExecution 和 return 响应。

public class ValidateModelFilter : Attribute, IAsyncResultFilter
    {
        public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
        {
            if (!context.ModelState.IsValid)
                throw new InvalidParametersException(context.ModelState.StringErrors());

            await next();
        }
    }