Search code examples
c#dependency-injectionfluentvalidationsimple-injector

Is this the right way to RegisterDecorator when some types have no implementation?


Using simple injector with the command pattern described here. Most commands have companion classes that implement fluent validation's AbstractValidator<TCommand>, which means they also implement FV IValidator<TCommand>. However it doesn't always make sense to have a validator implementation for every command.

As far as I can tell, the command decorator implementation cannot take IValidator<TCommand> as a constructor arg unless every ICommandHandler<TCommand> has a corresponding FV.IValidator<TCommand>. I tried the following:

public class FluentValidationCommandDecorator<TCommand> 
    : IHandleCommands<TCommand>
{
    public FluentValidationCommandDecorator(IHandleCommands<TCommand> decorated
        , IValidator<TCommand> validator
    )
    {
        _decorated = decorated;
        _validator = validator;
    }
    ...
}
...
container.RegisterManyForOpenGeneric(typeof(IValidator<>), assemblies);
container.RegisterDecorator(typeof(IHandleCommands<>),
    typeof(FluentValidationCommandDecorator<>),
    context =>
    {
        var validatorType =
            typeof (IValidator<>).MakeGenericType(
                context.ServiceType.GetGenericArguments());
        if (container.GetRegistration(validatorType) == null)
            return false;
        return true;
    });

Unit tests that run Container.Verify() once, pass. Unit tests that run Container.Verify() more than once, fail from an InvalidOperationException on the second invocation:

The configuration is invalid. Creating the instance for type 
IValidator<SomeCommandThatHasNoValidatorImplementation> failed. Object reference
not set to  an instance of an object.

The following works, by taking the Container as an argument:

public class FluentValidationCommandDecorator<TCommand> 
    : IHandleCommands<TCommand>
{
    private readonly IHandleCommands<TCommand> _decorated;
    private readonly Container _container;

    public FluentValidationCommandDecorator(Container container
        , IHandleCommands<TCommand> decorated
    )
    {
        _container = container;
        _decorated = decorated;
    }

    public void Handle(TCommand command)
    {
        IValidator<TCommand> validator = null;
        if (_container.GetRegistration(typeof(IValidator<TCommand>)) != null)
            validator = _container.GetInstance<IValidator<TCommand>>();

        if (validator != null) validator.ValidateAndThrow(command);

        _decorated.Handle(command);
    }
}
...
container.RegisterManyForOpenGeneric(typeof(IValidator<>), assemblies);
container.RegisterDecorator(typeof(IHandleCommands<>),
    typeof(FluentValidationCommandDecorator<>));

If this class didn't have to take a dependency on Simple Injector, I could move it into the domain project. The domain already takes a dependency on FluentValidation.net so that domain validity can be unit tested. I think this decorator belongs in the domain, but neither it nor its unit test project takes a dependency on simpleinjector (or should have to, since the domain is not the composition root).

Is there a way to tell simpleinjector to only decorate a CommandHandler<TCommand> instance with a FluentValidationCommandDecorator<TCommand> if there is an implementation registered for IValidator<TCommand>?


Solution

  • What you need is unregistered type resolution, to map missing types to a default implementation. Or in other words, you need to use the RegisterOpenGeneric method:

    container.RegisterOpenGeneric(typeof(IValidator<>), 
        typeof(NullValidator<>));
    

    Now you need to define a NullValidator<T> that implements IValidator<T> with an empty / default implementation.

    When you do this every time a certain (unregistered) IValidator<T> is requested, a new instance of NullValidator<T> will be returned. It will not override types that are registered using RegisterManyForOpenGeneric, since those types are explictly registered.

    When the NullValidator<T> implementation is thread-safe (which will usually be the case with a empty implementation), you can optimize construction by registering them as singleton:

    container.RegisterSingleOpenGeneric(typeof(IValidator<>), 
        typeof(NullValidator<>));
    

    You can read more information in the wiki: Registration of open generic types