向 MediatR 行为管道添加验证?

Posted

技术标签:

【中文标题】向 MediatR 行为管道添加验证?【英文标题】:Add validation to a MediatR behavior pipeline? 【发布时间】:2017-02-16 19:30:30 【问题描述】:

我正在使用 ASP.NET Core、内置容器和支持 "behavior" pipelines 的 MediatR 3:

public class MyRequest : IRequest<string>

    // ...


public class MyRequestHandler : IRequestHandler<MyRequest, string>

    public string Handle(MyRequest message)
    
        return "Hello!";
    


public class MyPipeline<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>

    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next)
    
        var response = await next();
        return response;
    


// in `Startup.ConfigureServices()`:
services.AddTransient(typeof(IPipelineBehavior<MyRequest,str‌​ing>), typeof(MyPipeline<MyRequest,string>))

我需要流水线中的 FluentValidation 验证器。在 MediatR 2 中,validation pipeline was created thus:

public class ValidationPipeline<TRequest, TResponse>
    : IRequestHandler<TRequest, TResponse>
    where TRequest : IRequest<TResponse>


    public ValidationPipeline(IRequestHandler<TRequest, TResponse> inner, IEnumerable<IValidator<TRequest>> validators)
    
        _inner = inner;
        _validators = validators;
    

    public TResponse Handle(TRequest message)
    
        var failures = _validators
            .Select(v => v.Validate(message))
            .SelectMany(result => result.Errors)
            .Where(f => f != null)
            .ToList();
        if (failures.Any())
            throw new ValidationException(failures);
        return _inner.Handle(request);
    


我现在如何为新版本执行此操作?如何设置使用哪个验证器?

【问题讨论】:

【参考方案1】:

流程完全一样,只需要换个接口就可以使用新的IPipelineBehavior&lt;TRequest, TResponse&gt;接口。

public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>

    private readonly IEnumerable<IValidator<TRequest>> _validators;

    public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
    
        _validators = validators;
    

    public Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next)
    
        var context = new ValidationContext(request);
        var failures = _validators
            .Select(v => v.Validate(context))
            .SelectMany(result => result.Errors)
            .Where(f => f != null)
            .ToList();

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

        return next();
    

对于验证器,您应该在内置容器中将所有验证器注册为IValidator&lt;TRequest&gt;,以便将它们注入行为中。如果不想一一注册,我建议你看看带来汇编扫描能力的伟大的Scrutor library。这样它就会自己找到验证器。

另外,在新系统中,您不再使用装饰器模式,您只需在容器中注册您的通用行为,MediatR 就会自动拾取它。它可能看起来像:

var services = new ServiceCollection();
services.AddMediatR(typeof(Program));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
var provider = services.BuildServiceProvider();

【讨论】:

我不明白的一件事(虽然我知道我可以注册一个特定的类型,因为你帮我弄清楚了另一个问题),为什么这些东西的所有示例都使用开放泛型进行注册?您肯定需要一个用于一组特定请求、处理程序等的管道,而不是为每个请求运行每个管道吗? 我想这取决于。例如,我很高兴将验证行为应用于所有请求。最坏的情况是,没有为这个特定的请求注册验证器,它变成了一个空操作,只是委托给下一个行为或实际的请求处理程序。保持它们的通用性也使注册更加简单。感谢您报告该问题,我从一些使用 Autofac 的代码中提取了该问题,ASP.NET Core 中的内置容器可能不支持数组。 例外不是免费的。这是一个糟糕的设计。 尽管表述简洁,但我倾向于同意维德勋爵的观点。我试图在我的管道中实现验证而不抛出异常。从设计的角度来看,我不能认同业务验证失败会导致异常的观点。这很容易实现,但感觉不对。一定有更好的办法。我将考虑向 IResponse 添加属性,考虑到管道处理程序中的开放泛型类型,这可能需要强制转换、反射或两者兼而有之。 @RahulDass 是的。这篇文章把它钉在了medium.com/the-cloud-builders-guild/… 一切顺利!【参考方案2】:

我已将 .net 核心集成打包到 nuget 中,请随意使用: https://www.nuget.org/packages/MediatR.Extensions.FluentValidation.AspNetCore

只需在配置部分插入:

services.AddFluentValidation(new[] typeof(GenerateInvoiceHandler).GetTypeInfo().Assembly);

GitHub

【讨论】:

这是我最喜欢的答案。【参考方案3】:

在新版本 (MediatR (>= 9.0.0)) 上,您可以执行以下操作:

public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse>

    private readonly IEnumerable<IValidator<TRequest>> _validators;

    public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
    
        _validators = validators;
    

    public Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
    
        var context = new ValidationContext<TRequest>(request);
        var failures = _validators
            .Select(v => v.Validate(context))
            .SelectMany(result => result.Errors)
            .Where(f => f != null)
            .ToList();

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

        return next();
    

记得在 FluentApi 8.0 或更低版本中添加var context = new ValidationContext&lt;TRequest&gt;(request);,它使用类似var context = new ValidationContext(request);的东西

用于在 IServiceCollection 下注册 Asp.Net Core 的代码如下:

services.AddMediatR(Assembly.GetExecutingAssembly());
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));

希望对您有所帮助!

【讨论】:

以上是关于向 MediatR 行为管道添加验证?的主要内容,如果未能解决你的问题,请参考以下文章

Mediatr 行为管道中的验证

Mediatr 3.0 使用管道行为进行身份验证

来自管道行为的 MediatR 流畅的验证响应

Mediatr:单元测试行为/验证

返回带有错误的响应,而不是在验证管道 mediatr 3 中抛出异常

如何为 ASP.NET Core 注册和使用 MediatR 管道处理程序?