danludwig danludwig - 2 months ago 9
C# Question

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>
?

Answer

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

Comments