You should treat the Command as only a DTO.
public class Command
{
public Foo SomeFoo { get;set; }
}
Then we use a visitor pattern on that dto
public interface ICommandHandler<in TCommand>
{
Task Handle(TCommand command);
}
edit:I got an downvote which I do not understand, its the most clean solution of the answers. And it does not involve type casting and using IsSubclassOf which in it self violate for example Open/closed principle of SOLID. With my solution you work with the IoC not against it. If you need a service just do
public class SendInvoiceCommandHandler : ICommandHandler<SendInvoiceCommand>
{
private readonly IInvoiceService _invoiceService;
public SendInvoiceCommandHandler(IInvoiceService invoiceService)
{
_invoiceService = invoiceService;
}
public async Task Handle(SendInvoiceCommand cmd)
{
await _invoiceService.Send(cmd.InvoiceId);
}
}
The implementation of ICommandHandler can freely call any service it want and inject it using its constructor.
At runtime I like to use magic to lookup the handlers so I just do
await _cqsClient.ExeceuteAsync(new SendInvoiceCommand(invoiceId));
If you use resharper I have made a plugin that helps when you build a system on visitor pattern like this. https://plugins.jetbrains.com/plugin/12156-resharpervisitorpatternnavigation
It can navigate directly from a instance of the DTO to the handler through a hot key in resharper.
So you need to have a IoC that can register concrete types more dynamic, some have it build in, other dont. I use the vanilla IoC in .NET Core and have written a extension method to the IServiceCollection.
.AddAllTransient(typeof(ICommandHandler<>), typeof(MyCommandThatPointsOutAssembly))
First parameter point out the interface, and the second one a type in assembly you want to want to scan for concrete types of said interface. I wont show that code. But it scans the assembly and registerer all ICommandHandler<T> it can find. I also register a cache for type look up at the same time. This is used from the command runner class like this
public CommandRunner(IServiceProvider serviceProvider, ILogger<CommandRunner> logger, Dictionary<Type, IEnumerable<RegistrationInfo>> typeScanners)
{
_serviceProvider = serviceProvider;
_logger = logger;
_cache = typeScanners[typeof(ICommandHandler<>)]
.Union(typeScanners[typeof(IQueryHandler<,>)])
.ToDictionary(info => info.Interface.GetGenericArguments()[0], info => info.Interface);
}
Basicly I build a cache were the DTO is the key, and the value is the concrete type.
Its super easy to execute the Handler once you have that info
private Task ExecuteCommandInternalAsync(Command cmd, IServiceProvider serviceProvider)
{
var handler = serviceProvider.GetService(_cache[cmd.GetType()]) as dynamic;
return handler.Handle(cmd as dynamic);
}