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.