Использование нескольких FluentValidators на конвейере MediatR - PullRequest
7 голосов
/ 21 октября 2019

У меня есть поведение конвейера 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();
    }

    ...
}

И команды MediatR, такие как:

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

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

    public class Handler<T>: IRequestHandler<T, CommandResponse>
    {
        ...
    }
}

Валидаторы зарегистрированы на Startup.cs следующим образом:

        AssemblyScanner
          .FindValidatorsInAssembly(Assembly.GetAssembly(typeof(MyUseCase)))
            .ForEach(result => 
                services.AddScoped(result.InterfaceType, result.ValidatorType));

Это хорошо работает для MyUseCase.Validator, оно внедряется в конвейер и выполняется, проверяя MyUseCase.Command.

Но это большое приложение, и многие команды имеют общие свойства,то есть каждая операция заказа получает OrderId, и я должен проверить, является ли идентификатор действительным, существует ли сущность в базе данных, является ли аутентифицированный пользователь владельцем изменяемого заказа и т. д.

Итак, япопытался создать следующий интерфейс и валидатор:

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()
    } 
}

Наконец я изменил команду на эту:

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

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

    public class Handler<T>: IRequestHandler<T, CommandResponse>
    {
        ...
    }
}

Проблема в том, что IOrderValidator не вводится в конвейер,только MyUseCase.Validator - это.

Я что-то здесь упускаю или вообще возможно добавить несколько валидаторов в конвейер?

1 Ответ

6 голосов
/ 23 октября 2019

Разрешение службы зависит от используемого вами DI-контейнера. Кажется, вы используете встроенный контейнер .NET Core, и он не может разрешать контравариантные интерфейсы.

Вместо этого рассмотрим Simple Injector , поскольку он знает, как работать с контравариантностью. Этот пример кода разрешит все необходимые вам валидаторы:

[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);
}

В качестве альтернативы вы должны явно зарегистрировать ваши валидаторы, параметризованные всеми командами, к которым они должны применяться:

[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);
}

Обратите внимание на разницу междуIOrder и Command для регистрации OrderValidator в случае Simple Injector и контейнера .NET Core DI:

container.Collection.Append<IValidator<IOrder>, OrderValidator>();
servcies.AddTransient(typeof(IValidator<Command>), typeof(OrderValidator))

Предполагается, что определены следующие классы и интерфейсы:

interface IOrder
{
}

class Command : IRequest<CommandResponse>, IOrder
{
}

class CommandResponse
{
}

class OrderValidator : AbstractValidator<IOrder>
{
}

class CommandValidator : AbstractValidator<Command>
{
}
...