【问题标题】:Double validation in MediatR pipelineMediatR 管道中的双重验证
【发布时间】:2017-02-21 14:47:32
【问题描述】:

我使用的是 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,如果无效则重定向到视图以供用户更正数据
  • 否则操作会运行
  • 命令被发送到管道中
  • 管道再次验证命令

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

我怎样才能最好地处理这个问题?

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

【问题讨论】:

  • HandleInvalidCommand 属性本身是否运行验证?
  • @MickaëlDerriey Nope FluentValidation 在模型绑定期间自动验证参数。该属性只检查ModelState.IsValid 并将视图重定向回。我进行了编辑以使其更清晰。
  • 好的。所以是的,我想您必须在作为模型绑定的一部分或作为 MediatR 管道的一部分运行验证之间做出选择。这真的是一种“视情况而定” 的情况。一方面,在模型绑定期间加入它可以更容易地在 MVC 级别处理错误。另一方面,将其作为 MediatR 管道的一部分使其成为一个横切关注点,因此如果您选择将管道插入另一个应用程序/框架中,则非常适用。不过,处理错误可能会更棘手。验证行为可能会引发操作过滤器可以捕获和处理的异常。
  • @MickaëlDerriey 是的,这就是问题所在。但我在下面添加了一个折衷的解决方案。当你有机会时,很想知道你对它的看法。

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


【解决方案1】:

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

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

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

【讨论】:

    【解决方案2】:

    我找到了另一种方法。也许不是最好的,但它确实有效。

    定义这个接口

    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 管道中,如果设置了该标志,则不会再次执行验证。

    但是我不确定我是否喜欢这样,因为我正在将不应该存在的东西泄漏到命令中。

    【讨论】:

    • 为什么不直接从应用中删除 Mediatr 验证管道?如果你用Interceptor处理每一个命令,验证管道就没用了。
    • @Pavel 在上面的编辑中查看我的解释:显而易见的方法是从管道中删除验证,但这并不好,因为命令可能来自 UI,也来自应用程序本身。并且您希望在这两种情况下都进行验证。
    • 当你说命令可能来自应用程序本身时,你的意思是你会从查询/命令处理程序发送第二个命令吗?
    • @MickaëlDerriey 也许吧。但总的来说,我的意思是我们有一个包含许多相互关联的部分的应用程序,其中只有一个是 UI(MVC 控制器等)。还有很多非 UI 的东西。其中一些还需要创建/编辑/等。实体和队列事物,并做事。那么为什么不重用命令和处理程序呢?他们已经在那里,已经测试过了。因此,如果命令来自应用程序本身,也需要对其进行验证。这种方式允许从 UI 以及应用程序中生成的内容进行验证。也许不是最好的设计,但它干净且可维护。
    • 在这种情况下,我建议从 MVC 中删除验证,所以它发生的唯一地方是 MediatR 管道。然后,您需要将验证错误从 FluentValidation 转换为 MVC 模型状态,并找到一种向用户显示错误页面的方法。我个人不喜欢双重验证的事情,即使在更改为只运行一次之后也是如此。
    【解决方案3】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-15
      相关资源
      最近更新 更多