Reputation: 103
I want to decorate just one MediatR Handler. I tried using Behaviours, but Behaviours inject the decorator for every handler that implements IRequestHandler<TRequest,TResponse>
public class ProcessFirstCommand : IRequest<bool>
{
public string Message { get; set; }
}
public class ProcessFirstCommandHandler : IRequestHandler<ProcessFirstCommand, bool>
{
public Task<bool> Handle(ProcessFirstCommand request, CancellationToken cancellationToken)
{
Console.WriteLine("Inside Process First Command Handler");
return Task.FromResult(true);
}
}
public class Manager
{
private readonly IMediator _mediator;
public Manager(IMediator mediator)
{
_mediator = mediator;
}
public void Execute()
{
_mediator.Send(new ProcessFirstCommand());
}
}
//Registering in Autofac for IRequestHandler
public class Module : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(ThisAssembly)
.AsClosedTypesOf(typeof(IRequestHandler<,>));
}
}
Question: How do I add a decorator that will execute before calling Handle method of ProcessFirstCommandHandler class and not for other classes that implement IRequestHandler.
How can I make the below class Handle method called first before ProcessFirstCommandHandler when the Manager objects executes this line _mediator.Send(new ProcessFirstCommand());
public class ProcessFirstCommandHandlerDecorator<TRequest, TResponse> : IRequestHandler<ProcessFirstCommand, bool>
where TRequest : ProcessFirstCommand
{
private readonly IRequestHandler<ProcessFirstCommand, bool> _handler;
public ProcessFirstCommandHandlerDecorator(IRequestHandler<ProcessFirstCommand, bool> handler)
{
_handler = handler;
}
public Task<bool> Handle(ProcessFirstCommand request, CancellationToken cancellationToken)
{
Console.WriteLine("Inside Process First Command Handler Decorator");
_handler.Handle(request, cancellationToken);
return Task.FromResult(true);
}
}
Upvotes: 6
Views: 9643
Reputation: 69
If all you are trying to do is get something to run before your handler is called then you can leverage Behaviors to implement this. I know you have said that you have tried this before, however, you can create a generic Behavior which runs all the implementations of IRequestPreProcessor.
Note: The below process also works for implementing something AFTER your handler runs, you simply need to change the interface from IRequestPreProcessor to IRequestPostProcessor
So if you have your command handler:
public class ProcessFirstCommandHandler : IRequestHandler<ProcessFirstCommand, bool>
{
public Task<bool> Handle(ProcessFirstCommand request, CancellationToken cancellationToken)
{
Console.WriteLine("Inside Process First Command Handler");
return Task.FromResult(true);
}
}
You can make an implementation of IRequestPreProcessor (your required decorator), but be sure to specify the command you want this to run against
public class PreProcessFirstCommand : IRequestPreprocessor<ProcessFirstCommand>
{
public ProcessFirstCommandHandlerDecorator()
{
}
public Task Process(ProcessFirstCommand request, CancellationToken cancellationToken)
{
Console.WriteLine("Inside Process First Command Handler Decorator");
}
}
This will be activated by your generic PreProcessorBehaviour which will run on every MediatR request but will only ever inject the implementations of IRequestPreProcessor which use a generic type or a specify the TRequest type as our PreProcessFirstCommand class above does:
public class RequestPreProcessValidationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse>
{
private readonly IEnumerable<IRequestPreProcessor<TRequest>> _preProcessors;
public RequestPreProcessValidationBehaviour(IEnumerable<IRequestPreProcessor<TRequest>> preProcessors)
{
_preProcessors = preProcessors;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
foreach (var processor in _preProcessors)
{
await processor.Process(request, cancellationToken).ConfigureAwait(false);
}
return await next().ConfigureAwait(false);
}
}
NOTE: The only slight snag this solution has is that if you are using ASP .NET Core's default Dependency Injector it will only ever inject one of the classes which implement IRequestPreProcessor AND specify a type.
For Example:
If you have the following classes:
public class ProcessFirstCommandHandler : IRequestHandler<ProcessFirstCommand, bool>
{
public Task<bool> Handle(ProcessFirstCommand request, CancellationToken cancellationToken)
{
Console.WriteLine("I'm inside the handler");
return Task.FromResult(true);
}
}
public class PreProcessFirstCommand : IRequestPreprocessor<ProcessFirstCommand>
{
public ProcessFirstCommandHandlerDecorator()
{
}
public Task Process(ProcessFirstCommand request, CancellationToken cancellationToken)
{
Console.WriteLine("I ran before the handler");
}
}
public class AnotherPreProcessFirstCommand : IRequestPreprocessor<ProcessFirstCommand>
{
public ProcessFirstCommandHandlerDecorator()
{
}
public Task Process(ProcessFirstCommand request, CancellationToken cancellationToken)
{
Console.WriteLine("I ran before the handler as well!");
}
}
public class GenericPreProcessCommand<TRequest> : IRequestPreprocessor<TRequest>
{
public ProcessFirstCommandHandlerDecorator()
{
}
public Task Process(ProcessFirstCommand request, CancellationToken cancellationToken)
{
Console.WriteLine("I'm generic!");
}
}
public class AnotherGenericPreProcessCommand<TRequest> : IRequestPreprocessor<TRequest>
{
public ProcessFirstCommandHandlerDecorator()
{
}
public Task Process(ProcessFirstCommand request, CancellationToken cancellationToken)
{
Console.WriteLine("I'm generic aswell!");
}
}
Using the generic PreProcessorBehavior mentioned earlier, this will inject both GenericPreProcessCommand and AnotherGenericPreProcessCommand, but only one of PreProcessFirstCommand or AnotherPreProcessFirstCommand. This seems to just be a limitation of the DI. I have left comments for MediatR creator Jimmy Bogard on the official github issue, so by all means please read and contribute there also.
Upvotes: 3
Reputation: 196
While using IRequestPreProcessor
is the recommended and preferred way in most of the cases, you can still call your IoC container directly to add decorators.
For example, using SimpleInjector:
container.RegisterDecorator(typeof(IRequestHandler<,>), typeof(HandlerDecorator<,>));
Upvotes: 1