2
votes

I'm building a query pipeline (using the decorator pattern for an IQueryHandler) in which, before the query is actually executed, a number of cross-cutting concerns is handled. One of these concerns is validation, for which I'm using the Fluent Validation Library.

Simple Injector is the IoC container of choice and I use it to register every IQueryValidationHandler using generic contravariance and the following registration:

container.RegisterManyForOpenGeneric(typeof(IQueryValidationHandler<>), container.RegisterAll, _assemblies);

This works perfectly for resolving the following implementation:

public class QueryValidationHandler : IQueryValidationHandler<IQuery>{ }

The query available in the class is a concrete type of IQuery. However, when I try to inject an IValidator (through constructor-injection) based on IQuery into the QueryValidationHandler, using the following code and registration:

Code:

public class QueryValidationHandler : IQueryValidationHandler<IQuery>
{
    private IValidator< IQuery > _validator;

    public QueryValidationHandler(IValidator<IQuery> validator)
    {
        _validator = validator;
    }

Registration:

container.RegisterManyForOpenGeneric(typeof (IValidator<>), container.RegisterAll, _assemblies);

I receive the following error:

The constructor of type QueryValidationHandler contains the parameter of type IValidator<IQuery> with name 'validator' that is not registered. Please ensure IValidator<IQuery> is registered in the container, or change the constructor of QueryValidationHandler.

Is there a way to get an implementation for IValidator for the concrete type of IQuery that's injected into the QueryValidationHandler?

Update

After Steven's excellent answer I am still stuck (with same exception). Most likely because I'm trying to register implementations of Fluent Validators AbstractValidator< T > as IValidator< T >. In previous projects I was able to do the following registration:

container.RegisterManyForOpenGeneric(typeof(IValidator<>), typeof(FindProductsForCompanyQuery).Assembly);

For the following implementations:

public class FindProductsForCompanyQueryValidator : AbstractValidator<FindProductsForCompanyQuery>
{
    public FindProductsForCompanyQueryValidator()
    {
        RuleFor(q => q.CompanyId).NotNull();
    }
}

The QueryValidationHandler mentioned above should be injected with all the AbstractValidator implementations in the assembly, so it can check for errors and throw an exception if validation fails.

1

1 Answers

6
votes

The exception occurs because you never made a registration for an IValidator<T>; you only made registrations for IEnumerable<Validator<T>>. Simple Injector differentiates registration of collections from 'normal' registrations.

Since you request an IValidator<IQuery>, Simple Injector expects there to be a registration for IValidator<IQuery>, usually registered using Register<IValidator<IQuery>, SomeImplementation>(). However, you registered collections of IValidator<T> (note the container.RegisterAll within the RegisterManyForOpenGeneric), which allows Simple Injector to inject a collection.

The real question here is of course: what type of registration do you need? What kind of mapping should there be for IValidator<T>? Do you need an:

  • One-to-one mapping, where there is exactly one implementation for each closed generic version of IValidator<T>?
  • One-to-zero-or-one mapping, where there is possibly one implementation (or none) for each closed generic version of IValidator<T>?
  • One-to-many mapping, where there are zero, one or more implementations for each closed generic version?

For each kind, you need a different registration. For One-to-one, you need the following:

// Simple Injector v3.x
container.Register(typeof(IValidator<>), _assemblies);

// Simple Injector v2.x
container.RegisterManyForOpenGeneric(typeof(IValidator<>), _assemblies);

The default behavior of Register while passing in a list of assemblies is to call Register(serviceType, ImplementationType) on each found implementation. Since Simple Injector does not allow multiple registrations for the same service type (because that's what RegisterCollection is for), the registration will fail if you (accidentally) have a second validator for the same closed generic type. This is a very useful safety measure.

With validation however, it is quite usual to have validation for just some of your queries. Not all queries need to have a specific validator. Using the one-to-one mapping would be very annoying, because you will have to define many empty validators for every query that doesn't have any validation at all. For this scenario, Simple Injector allows you to register a fallback registration that will be selected in case there is no registration:

// Simple Injector v3.x
container.Register(typeof(IValidator<>), _assemblies);
container.RegisterConditional(typeof(IValidator<>), typeof(EmptyValidator<>),
    c => !c.Handled);

// Simple Injector v2.x
container.RegisterManyForOpenGeneric(typeof(IValidator<>), _assemblies);
container.RegisterOpenGeneric(typeof(IValidator<>), typeof(EmptyValidator<>));

Here we register an open generic EmptyValidator<T> as fallback registration, which means it will be picked up in case there is no explicit registration available.

The third case, one-to-many, is what you are already using:

// Simple Injector v3.x
container.RegisterCollection(typeof(IValidator<>), _assemblies);

// Simple Injector v2.x
container.RegisterManyForOpenGeneric(typeof(IValidator<>),
    container.RegisterAll, _assemblies);

But if you register a collection, you either need to inject an enumerable, or you need to create a composite validator.

Example of injecting an IEnumerable:

public class QueryValidationHandler : IQueryValidationHandler<IQuery>
{
    private IEnumerable<IValidator<IQuery>> _validators;

    public QueryValidationHandler(IEnumerable<IValidator<IQuery>> validators) {
        _validators = validators;
    }
}

A composite validator is very useful, especially when you have multiple classes where those validators need to be injected into. In that case you don't want the application to know about the existence of the fact that there are multiple validators (that's an implementation detail), just as looping over all the validators is an implementation detail as well; it violates the Dependency Inversion Principle. Besides, it validates DRY since you are duplicating code. Always a bad thing.

So what you can do is create a composite:

public class CompositeValidator<T> : IValidator<T>
{
    private IEnumerable<IValidator<T>> _validators;
    public CompositeValidator(IEnumerable<IValidator<T>> validators) {
        _validators = validators;
    }

    public IEnumerable<ValidationError> Validate(T instance) {
        return
            from validator in _validators
            from error in validator.Validate(instance)
            select error;
    }
}

With this CompositeValidator<T> you will get the following registration:

container.RegisterCollection(typeof(IValidator<>), _assemblies);
container.Register(typeof(IValidator<>), typeof(CompositeValidator<>),
    Lifestyle.Singleton);

This is one of the many advantages of Simple Injector's design where registration of collections are separated from one-to-one registrations. It makes it really straightforward to register composites. Registering such composite is much harder with containers that don't have this clear separation, because with those containers, without doing anything special to your configuration, one of the validators injected into the CompositeValidator<T> will be the CompositeValidator<T> itself, which will eventually result in a stack overflow exception.

And your query handler can now again simply depend on IValidator<T>:

public class QueryValidationHandler : IQueryValidationHandler<IQuery>
{
    private IValidator<IQuery> _validator;

    public QueryValidationHandler(IValidator<IQuery> validator) {
        _validator = validator;
    }
}

Update:

Since you have a non-generic QueryValidationHandler that should be able to validate ANY query, injecting an IValidator<IQuery> will not work, because your DI library has no clue what validator implementation(s) it needs to inject. Instead you need to use a mediator that can delegate the validation to a real validator. For instance:

sealed class Validator : IValidator // note: non-generic interface
{
    private readonly Container container;

    public Validator(Container container) {
        this.container = container;
    }

    [DebuggerStepThrough]
    public IEnumerable<ValidationError> Validate(object instance) {
        var validators = container.GetAllInstances(
            typeof(IValidator<>).MakeGenericType(instance.GetType()));

        return
            from validator in validators
            from error in Validate(validator, instance)
            select error;
    }

    private static IEnumerable<ValidationError> Validate(
        dynamic validator, dynamic instance) {
        return validator.Validate(instance);
    }
}

Your consumers can now depend upon the non-generic IValidator interface and get an Validator injected. This validator will forward the calls to any IValidator<T> implementations that might exist.