MediatR管道中的双重验证

时间:2017-02-21 14:47:33

标签: c# asp.net-core cqrs fluentvalidation mediatr

我在ASP.NET Core和新的MediatR which supports pipelines上。我的管道includes validation

考虑这个动作:

[HttpPost]
[HandleInvalidCommand]
public IActionResult Foo(Command command)
{
    await _mediator.Send(command);
    return View();
}
  • 验证命令(我使用FluentValidation)
  • HandleInvalidCommand检查ModelState.IsValid,如果无效,则重定向到视图以供用户更正数据
  • 其他操作
  • 该命令被发送到管道
  • 管道验证命令AGAIN

因此,如果命令有效,则验证会发生两次(验证器的运行成本很高)。

我如何才能最好地解决这个问题?

编辑:显而易见的方法是从管道中删除验证,但这并不好,因为该命令可能来自UI,但也来自应用程序本身。并且您希望在两种情况下进行验证。

3 个答案:

答案 0 :(得分:1)

FluentValidation即使验证失败也不会停止处理您的命令 - 它只是注册规则。

Mediatr Validation Pipeline检查现有验证错误并停止发送命令 - 如果存在错误,Handler将不会触发。

但是你实现了自己的逻辑 - HandleInvalidCommand。您应该选择一个选项 - mediatr pipiline或使用ModelState.IsValid实现自己的逻辑

答案 1 :(得分:1)

我发现了另一种方式。也许不是最好的,但它确实有效。

定义此界面

public interface IValidated
{
    bool AlreadyValidated { get; }
}

装饰请求

public class Command : IRequest, IValidated
{
    public bool AlreadyValidated { get; set; }
    // etc...
}

将请求的验证程序更新为use an interceptor

public class CommandValidator : AbstractValidator<Command>, IValidatorInterceptor
{

    public CommandValidator() { 
        // validation rules etc.
    }

    public ValidationContext BeforeMvcValidation(ControllerContext controllerContext, ValidationContext validationContext)
    {
        return validationContext;
    }


    public ValidationResult AfterMvcValidation(ControllerContext controllerContext, ValidationContext validationContext, ValidationResult result)
    {
        var command = validationContext.InstanceToValidate as Command;
        if (command != null) command.AlreadyValidated = true;
        return result;
    }

}

更新管道:

public class MyPipeline<TRequest, TResponse>
    : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>, IValidated   // update here
{

    public async Task<TResponse> Handle(
        TRequest message,
        RequestHandlerDelegate<TResponse> next)
    {

        if (!message.AlreadyValidated)      // update here
        {
            var context = new ValidationContext(message);
            var failures = _validators
                .Select(v => v.Validate(context))
                .SelectMany(e => e.Errors)
                .Where(e => e != null)
                .ToList();

            if (failures.Count != 0)
                throw new ValidationException(failures);
        }

      return await next();
      }

}

因此,在通过MVC / FluentValidation进行验证后,它会设置标志。然后在CQRS管道中,如果设置了该标志,则不再执行验证。

但是我不确定我喜欢这个,因为我正在泄漏那些不应该存在的命令。

答案 2 :(得分:0)

我认为理想的解决方案是将命令/查询与来自客户端的模型之间使用的类分开。这可能是最正确的设计,因为它使命令/查询类专用于应用程序核心输入,因此不会进行修改以适合客户端并随时间变化。当涉及到应用程序核心时,这将使您的CQRS类更加纯净。

但这确实意味着要重复更多的类,以便为客户的输入提供更多的类。