8

I have a MediatR pipeline behavior like this:

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

    ...
}

And MediatR commands like this:

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

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

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

The validators are registered on Startup.cs like this:

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

This works nice for the MyUseCase.Validator, it is injected on the pipeline and is executed, validating the MyUseCase.Command.

But it's a large application, and many commands have common properties, i.e. every order operation receives an OrderId and I have to check if the Id is valid, if the entity exists in database, if the authenticated user is the owner of the order being modified, etc.

So I tried to create the following interface and validator:

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

Finally I changed the command to this:

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

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

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

The problem is that the IOrderValidator is not injected in the pipeline, only the MyUseCase.Validator is.

Am I missing something here or is it even possible to inject multiple validators in the pipeline?

Collin Barrett
  • 2,441
  • 5
  • 32
  • 53
rbasniak
  • 4,484
  • 11
  • 51
  • 100
  • 1
    all you need to do is register `IOrderValidator` as an `IValidator` basically change the code of the assembly scanner – johnny 5 Oct 23 '19 at 18:16

1 Answers1

6

Service resolution depends on the DI container that you use. It seems that you use built-in .NET Core container and it cannot resolve contravariant interfaces.

Consider Simple Injector instead as it knows how to work with contravariance. This sample code will resolve all the validators you need:

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

Alternatively you have to explicitly register your validators parameterized with all commands they must apply to:

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

Note the difference between IOrder and Command for OrderValidator registration in case of Simple Injector and .NET Core DI container:

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

Assuming following classes and interfaces are defined:

interface IOrder
{
}

class Command : IRequest<CommandResponse>, IOrder
{
}

class CommandResponse
{
}

class OrderValidator : AbstractValidator<IOrder>
{
}

class CommandValidator : AbstractValidator<Command>
{
}
Andrii Litvinov
  • 12,402
  • 3
  • 52
  • 59
  • 1
    .Net-Core DI has the same support – johnny 5 Oct 23 '19 at 18:13
  • 1
    all you need to do is register IOrderValidator as an IValidator – johnny 5 Oct 23 '19 at 18:16
  • 1
    @johnny5, nope, you can try yourself. Install required MediatR, FluentValidation and FluentAssertion packages and run the test. – Andrii Litvinov Oct 23 '19 at 18:32
  • 1
    Oh I see now, `Command` inherits from `IOrder` you're saying the list of Validators isn't populated properly because `CommandValidator` is an `IValidator` instead of `IValidator` – johnny 5 Oct 23 '19 at 19:18
  • @johnny5, yeah, and the MediatR resolves validator based on concrete command type, not the interface. So in .NET Core container both validators have to be registered as `IValidator`. And same has to be done for all commands that have to be validated by `OrderValidator`. – Andrii Litvinov Oct 24 '19 at 07:50