'Decorate MediatR IRequestHandler using Microsoft DI
I'm trying to decorate some of my MediatR's IRequestHandlers. To mark handlers I wanted to decorate, I created the interface ICommandHandler that inherits from IRequestHandler and ICommand that inherits from IRequest.
public interface ICommand : IRequest { }
public interface ICommandHandler<in TCommand> : IRequestHandler<TCommand>
where TCommand : ICommand { }
Now I want every ICommandHandler to be decorated with the UnitOfWorkCommandHandlerDecorator.
internal class UnitOfWorkCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand>
where TCommand : ICommand
{
private readonly ICommandHandler<TCommand> _decorated;
private readonly IUnitOfWork _unitOfWork;
public UnitOfWorkCommandHandlerDecorator(ICommandHandler<TCommand> decorated, IUnitOfWork unitOfWork)
{
_decorated = decorated;
_unitOfWork = unitOfWork;
}
public async Task<Unit> Handle(TCommand request, CancellationToken cancellationToken)
{
var res = await _decorated.Handle(request, cancellationToken);
await _unitOfWork.CommitAsync();
return res;
}
}
I tried registering a decorator with Scrutor's decorate method, however the decorator isn't invoked when a request is sent with IMediator.Send. That's probably because it's an ICommandHandler decorator rather than an IRequestHandler decorator.
services.Decorate(typeof(ICommandHandler<>), typeof(UnitOfWorkCommandHandlerDecorator<>));
Is it possible with Microsoft DI or do I need another DI container?
Solution 1:[1]
From what I read it is not possible, with Microsofts DI, to implement a decoration of an open generic on an open generic. But you can decorate a closed generic with an open generic. That leads to the idea that you scan your assembly for the generic, find the closed implementation and then decorate that.
services.Scan(scan =>
scan.FromAssembliesOf(typeof(ICommandHandler<>))
.AddClasses(classes =>
classes.AssignableTo(typeof(ICommandHandler<>)).Where(_ => !_.IsGenericType))
.AsImplementedInterfaces()
.WithTransientLifetime());
I cannot take any credit for the code, that comes from the GitHub ticket: https://github.com/khellang/Scrutor/issues/68
Solution 2:[2]
As I expected, the problem was decorating ICommandHandler when MediatR requests IRequestHandler from Microsoft DI.
I found two approaches to making this work:
- When MediatR requests an IRequestHandler, have Microsoft DI return a decorated ICommandHandler.
// Register all concrete classes implementing ICommandHandler as ICommandHandlers.
services.Scan(x => x.FromAssembliesOf(typeof(ICommand))
.AddClasses(classes => classes.AssignableTo(typeof(ICommandHandler<>))
.Where(t => !t.IsGenericType))
.AsImplementedInterfaces(t => t.IsGenericType
&& t.GetGenericTypeDefinition() == typeof(ICommandHandler<>))
.WithTransientLifetime());
// Decorate registered ICommandHandlers with open generic decorator.
services.Decorate(typeof(ICommandHandler<>), typeof(UnitOfWorkCommandHandlerDecorator<>));
// Get registered ICommandHandlers service descriptors from the container.
var registeredCommandHandlers = services
.Where(x => x.ServiceType.IsGenericType
&& x.ServiceType.GetGenericTypeDefinition() == typeof(ICommandHandler<>))
.ToArray();
foreach(var serviceDescriptor in registeredCommandHandlers)
{
// Find the IRequestHandler type of registered ICommandHandler.
var requestHandlerType = serviceDescriptor.ServiceType.GetInterfaces()
.First(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IRequestHandler<,>));
// Register a decorated ICommandHandler to be returned for IRequestHandler.
services.Add(new ServiceDescriptor(requestHandlerType,
sp => sp.GetRequiredService(serviceDescriptor.ServiceType), serviceDescriptor.Lifetime));
}
// Invoke AddMediatR method to register IRequestHandlers that are not ICommandHandlers
// and others required by MediatR classes.
services.AddMediatR(typeof(TestCommandHandler));
- Change MediatR to request ICommandHandler rather than IRequestHandler.
// Register all concrete classes implementing ICommandHandler as ICommandHandlers.
services.Scan(x => x.FromAssembliesOf(typeof(ICommand))
.AddClasses(classes => classes.AssignableTo(typeof(ICommandHandler<,>))
.Where(t => !t.IsGenericType))
.AsImplementedInterfaces(t => t.IsGenericType
&& t.GetGenericTypeDefinition() == typeof(ICommandHandler<,>))
.WithTransientLifetime());
// Decorate registered ICommandHandlers with open generic decorator.
services.Decorate(typeof(ICommandHandler<,>), typeof(UnitOfWorkCommandHandlerDecorator<,>));
// Invoke AddMediatR method to register IRequestHandlers that are not ICommandHandlers
// and others required by MediatR classes.
services.AddMediatR(typeof(TestCommandHandler));
// Register ServiceFactory delegate, which MediatR uses to resolve all services.
services.AddTransient<ServiceFactory>(sp =>
{
return t =>
{
if (t.IsGenericType
&& t.GetGenericTypeDefinition() == typeof(IRequestHandler<,>))
{
var genericArguments = t.GetGenericArguments();
var commandHandlerType = typeof(ICommandHandler<,>).MakeGenericType(genericArguments);
// Return ICommandHandler or IRequestHandler if it is not registered.
return sp.GetService(commandHandlerType)
?? sp.GetRequiredService(t);
}
return sp.GetRequiredService(t);
};
});
Personally, I like the first approach better; it is easier to understand, and it does not have as much overhead on runtime.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Tod |
| Solution 2 | grzegorzorwat |
