在MediatR管道上使用多个FluentValidator

RBa*_*iak 7 c# fluentvalidation mediatr asp.net-core

我有一个MediatR管道行为,如下所示:

public class FailFastRequestBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
    private readonly IEnumerable<IValidator> _validators;

    public FailFastRequestBehavior(IEnumerable<IValidator<TRequest>> validators)
    {
        _validators = validators;
    }

    public Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
    {
        var failures = _validators
            .Select(async v => await v.ValidateAsync(request))
            .SelectMany(result => result.Result.Errors)
            .Where(f => f != null);


        return failures.Any()
            ? Errors(failures)
            : next();
    }

    ...
}
Run Code Online (Sandbox Code Playgroud)

像这样的MediatR命令:

public class MyUseCase
{
    public class Command : IRequest<CommandResponse>
    {
        ...
    }

    public class Validator : AbstractValidator<Command>
    {
        ...
    }

    public class Handler<T>: IRequestHandler<T, CommandResponse>
    {
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

验证器的注册方式Startup.cs如下:

        AssemblyScanner
          .FindValidatorsInAssembly(Assembly.GetAssembly(typeof(MyUseCase)))
            .ForEach(result => 
                services.AddScoped(result.InterfaceType, result.ValidatorType));
Run Code Online (Sandbox Code Playgroud)

这对的效果很好,将MyUseCase.Validator其注入管道中并执行,验证MyUseCase.Command

但这是一个大型应用程序,许多命令具有共同的属性,即每个订单操作都收到一个,OrderId并且我必须检查Id是否有效,数据库中是否存在该实体,是否已通过身份验证的用户是要修改的订单的所有者,等等

因此,我尝试创建以下接口和验证器:

public interface IOrder
{
    string OrderId { get; set; }
}

public class IOrderValidator : AbstractValidator<IOrder>
{
    public IOrderValidator()
    {
        CascadeMode = CascadeMode.StopOnFirstFailure;

        RuleFor(x => x.OrderId)
            .Rule1()
            .Rule2()
            .Rule3()
            .RuleN()
    } 
}
Run Code Online (Sandbox Code Playgroud)

最后,我将命令更改为:

public class MyUseCase
{
    public class Command : IRequest<CommandResponse>: IOrder
    {
        ...
    }

    public class Validator : AbstractValidator<Command>
    {
        ...
    }

    public class Handler<T>: IRequestHandler<T, CommandResponse>
    {
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

问题在于,IOrderValidator不是仅将MyUseCase.Validatoris 注入管道中。

我是否在这里遗漏了一些东西,甚至有可能在管道中注入多个验证器?

And*_*nov 6

服务分辨率取决于您使用的DI容器。似乎您使用了内置的.NET Core容器,并且它无法解析相反的接口。

请考虑使用简单喷射器,因为它知道如何使用协变。此示例代码将解析您需要的所有验证器:

[Fact]
public void Test()
{
    var container = new SimpleInjector.Container();

    container.Collection.Append<IValidator<IOrder>, OrderValidator>();
    container.Collection.Append<IValidator<Command>, CommandValidator>();

    var validators = container.GetAllInstances<IValidator<Command>>();

    validators.Should().HaveCount(2);
}
Run Code Online (Sandbox Code Playgroud)

另外,您必须显式注册使用所有必须应用于以下命令的参数化验证器:

[Fact]
public void Test()
{
    var provider = new ServiceCollection()
        .AddTransient(typeof(IValidator<Command>), typeof(OrderValidator))
        .AddTransient(typeof(IValidator<Command>), typeof(CommandValidator))
        .BuildServiceProvider();

    var validators = provider.GetServices<IValidator<Command>>();

    validators.Should().HaveCount(2);
}
Run Code Online (Sandbox Code Playgroud)

请注意IOrderCommand对于OrderValidatorSimple Injector和.NET Core DI容器,它们之间的区别在于:

container.Collection.Append<IValidator<IOrder>, OrderValidator>();
servcies.AddTransient(typeof(IValidator<Command>), typeof(OrderValidator))
Run Code Online (Sandbox Code Playgroud)

假设定义了以下类和接口:

interface IOrder
{
}

class Command : IRequest<CommandResponse>, IOrder
{
}

class CommandResponse
{
}

class OrderValidator : AbstractValidator<IOrder>
{
}

class CommandValidator : AbstractValidator<Command>
{
}
Run Code Online (Sandbox Code Playgroud)