MediatR 管道中的双重验证
Double validation in MediatR pipeline
我在 ASP.NET Core 和新的 MediatR which supports pipelines. My pipeline 。
考虑这个动作:
[HttpPost]
[HandleInvalidCommand]
public IActionResult Foo(Command command)
{
await _mediator.Send(command);
return View();
}
- 命令已验证(我正在使用 FluentValidation)
HandleInvalidCommand
检查 ModelState.IsValid
,如果无效则重定向到用户更正数据的视图
- 否则动作运行s
- 命令被送入管道
- 管道再次验证命令
因此,如果命令有效,则验证会发生两次(并且验证器对 运行 而言是昂贵的)。
我怎样才能最好地处理这个问题?
编辑:显而易见的方法是从管道中删除验证,但这并不好,因为命令可能来自 UI,但也来自应用程序本身。在这两种情况下您都需要验证。
FluentValidation
即使验证失败也不会停止处理您的命令 - 它只是注册规则。
Mediatr Validation Pipeline
检查现有的验证错误并停止发送命令 - 如果存在错误,处理程序将不会触发。
但是您实现了自己的逻辑 - HandleInvalidCommand
。您应该选择 one 选项 - mediatr pipiline 或使用 ModelState.IsValid
实现自己的逻辑
我找到了另一种方法。也许不是最好的,但它确实有效。
定义这个接口
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 管道中,如果设置了该标志,则不会再次执行验证。
但是我不确定我是否喜欢这个,因为我正在将不应该存在的内容泄漏到命令中。
我认为理想的解决方案是将您在 command/query 和来自客户端的模型之间使用的 类 分开。这可能是最正确的设计,因为它使您的 command/query 类 专用于您的应用程序核心输入,因此不会被修改以适应客户并随时间变化。当涉及到您的应用程序核心时,这将使您的 CQRS 类 更加纯净。
然而,这确实意味着 类 的更多重复,以便为客户的输入提供更多 类。
我在 ASP.NET Core 和新的 MediatR which supports pipelines. My pipeline
考虑这个动作:
[HttpPost]
[HandleInvalidCommand]
public IActionResult Foo(Command command)
{
await _mediator.Send(command);
return View();
}
- 命令已验证(我正在使用 FluentValidation)
HandleInvalidCommand
检查ModelState.IsValid
,如果无效则重定向到用户更正数据的视图- 否则动作运行s
- 命令被送入管道
- 管道再次验证命令
因此,如果命令有效,则验证会发生两次(并且验证器对 运行 而言是昂贵的)。
我怎样才能最好地处理这个问题?
编辑:显而易见的方法是从管道中删除验证,但这并不好,因为命令可能来自 UI,但也来自应用程序本身。在这两种情况下您都需要验证。
FluentValidation
即使验证失败也不会停止处理您的命令 - 它只是注册规则。
Mediatr Validation Pipeline
检查现有的验证错误并停止发送命令 - 如果存在错误,处理程序将不会触发。
但是您实现了自己的逻辑 - HandleInvalidCommand
。您应该选择 one 选项 - mediatr pipiline 或使用 ModelState.IsValid
我找到了另一种方法。也许不是最好的,但它确实有效。
定义这个接口
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 管道中,如果设置了该标志,则不会再次执行验证。
但是我不确定我是否喜欢这个,因为我正在将不应该存在的内容泄漏到命令中。
我认为理想的解决方案是将您在 command/query 和来自客户端的模型之间使用的 类 分开。这可能是最正确的设计,因为它使您的 command/query 类 专用于您的应用程序核心输入,因此不会被修改以适应客户并随时间变化。当涉及到您的应用程序核心时,这将使您的 CQRS 类 更加纯净。
然而,这确实意味着 类 的更多重复,以便为客户的输入提供更多 类。