I have the following situation:
public interface ICommand { }
public interface ICommandHandler<TCommand> where TCommand : ICommand
{
void Handle(TCommand command);
}
public interface ISepsCommandHandler<TCommand> : ICommandHandler<TCommand>
where TCommand : ICommand
{
event EventHandler<EntityExecutionLoggingEventArgs> UseCaseExecutionProcessing;
}
public sealed class CalculateNewAverageElectricEnergyProductionPriceCommandHandler
: BaseCommandHandler,
ISepsCommandHandler<CalculateNewAverageElectricEnergyProductionPriceCommand>
{ ... }
public sealed class CalculateCpiCommandHandler
: BaseCommandHandler, ISepsCommandHandler<CalculateNewConsumerPriceIndexCommand>
{ ... }
In controllers I have multiple CommandHandler
s and QueryHandler
s in constructor and I would like to shorten it to a mediator pattern like with MediatR.
public interface ICqrsMediator // <TCommand, TQuery, TQueryResult>
// where TCommand : ICommand
{
void Send(ICommand command);
}
public class CqrsMediator : ICqrsMediator // <ICommand
// where TCommand : ICommand
{
private readonly IDictionary<Type, ICommandHandler<ICommand>> _commands;
public CqrsMediator(
IEnumerable<ICommandHandler<ICommand>> commands) { ... }
...
}
Problem:
I would like to resolve a collection of ICommandHandler
into CqrsMediator
's constructor. I have tried a variety of ways.
Question(s): Why doesn't this work in SI?
var bla = GetAllInstances(typeof(ICommandHandler<ICommand>));
I get a message that it cannot find ICommandHandler<ICommand>
but that ICommandHandler<TCommand>
is registered, although I have given a constraint in generics that TCommand can only be of type ICommand.
Can anybody help with the construction of the mediator pattern for CommandHandlers and QueryHandlers?
Why doesn't this work in SI?
This doesn't work for the same reason as this doesn't work in .NET. This would only work when your ICommandHandler<T>
interface would be defined as covariant, but that is impossible because the TCommand
is an input argument.
Let's remove the DI Container from the picture for a moment. Using plain-old C# code, the following is what you would like to accomplish:
ICommandHandler<ICommand> handler1 = new Command1Handler();
ICommandHandler<ICommand> handler2 = new Command2Handler();
ICommandHandler<ICommand> handler3 = new Command3Handler();
IEnumerable<ICommandHandler<ICommand>> handlers = new[] { handler1, handler2, handler3 };
new CqrsMediator(handlers);
The previous snippet creates three new command handlers:
Command1Handler
implements ICommandHandler<Command1>
Command2Handler
implements ICommandHandler<Command2>
Command3Handler
implements ICommandHandler<Command3>
Because you want to inject them into the CqrsMediator
, you place them in variables of type ICommandHandler<ICommand>
. This way you can easily construct an array (ICommandHandler<ICommand>[]
), which can be injected into the CqrsMediator
.
This code, however, does not compile. The C# compiler will state the following:
Error CS0266: Cannot implicitly convert type 'Command1Handler' to 'ICommandHandler<ICommand>'. An explicit conversion exists (are you missing a cast?)
This is the source of your problem. Your command handlers can't be cast from, for instance, ICommandHandler<Command1>
to ICommandHandler<ICommand>
. To understand this, you need to learn about covariance and contravariance. You might want to start here.
To allow ICommandHandler<Command1>
to be assignable to an ICommandHandler<ICommand>
it requires the ICommandHandler<TCommand>
abstraction to be covariant:
public interface ICommandHandler<out TCommand> where TCommand : ICommand { ... }
In other words, you need to make TCommand
an out
argument.
But this can't be done, because TCommand
is actually an in
argument (thus contravariant).
Long story short, due to the way variance and generics works in .NET (and I would actually say: in math) this is impossible.
There are two simple solutions for your problem, however.
CqrsMediator.Send
method generic:public class CqrsMediator : ICqrsMediator
{
private readonly Container container;
public CqrsMediator(Container container) => this.container = container;
public void Send<TCommand>(TCommand command)
{
var handler = this.container.GetInstance<ICommandHandler<TCommand>>();
handler.Handle(command);
}
}
Send
method generic is not an option, you can also use reflection inside the Send
method to find the command's correct type:public class CqrsMediator : ICqrsMediator
{
private readonly Container container;
public CqrsMediator(Container container) => this.container = container;
public void Send<TCommand>(TCommand command)
{
var handlerType = typeof(ICommandHandler<>).MakeGenericType(command.GetType());
dynamic handler = container.GetInstance(handlerType);
return handler.Handle((dynamic)command);
}
}
In both cases you let the CqrsMediator
implementation depend on the Container
. This means the implementation becomes an infrastructural component; it becomes part of your Composition Root.