Reputation: 687
[NOTE: This is a "replacement" question. The first one was based on my main project's code so I've redone the question with code from a single-purpose project that illustrates the principle more cleanly. The question remains the same, just better presented.]
I'm trying to setup a command pre-processor on a CQRS request pipeline using MediatR pipeline behaviors and Autofac for request routing. My goal is for the pre-processor to run only for commands (ICommand<>) as opposed to all requests (IRequest<>), which will result in the pre-processor executing for commands, queries and events.
I can get my GenericPreProcessor or any other pre-processor to run fine for all types of requests, but any method I've used to try to "filter" the injection either returns an error or simply doesn't execute the desired pre-processor.
My working-for-all-requests pipeline configuration in Autofac looks like this:
// Pipeline pre/post processors
builder
.RegisterGeneric(typeof(RequestPostProcessorBehavior<,>))
.As(typeof(IPipelineBehavior<,>));
builder
.RegisterGeneric(typeof(RequestPreProcessorBehavior<,>))
.As(typeof(IPipelineBehavior<,>));
// Works as desired: Fires generic pre-processor for ALL requests, both cmd and query
builder
.RegisterGeneric(typeof(GenericRequestPreProcessor<>))
.As(typeof(IRequestPreProcessor<>));
// Works for all requests, but I need a way to limit it to commands
builder
.RegisterGeneric(typeof(MyCommandPreProcessor<>))
.As(typeof(IRequestPreProcessor<>));
Conceptually I'm trying to do something like any of these, which fail:
builder
.RegisterGeneric(typeof(MyCommandPreProcessor<>)) // Note generic
.As(typeof(IRequestPreProcessor<ICommand<>>));
// Intellisense error "Unexpected use of an unbound generic"
builder
.RegisterType(typeof(MyCommandPreProcessor)) // Note non-generic
.As(typeof(IRequestPreProcessor<ICommand<>>));
// Intellisense error "Unexpected use of an unbound generic"
builder
.RegisterType(typeof(MyCommandPreProcessor)) // Note non-generic
.As(typeof(IRequestPreProcessor<ICommand<CommonResult>>));
// No errors, but MyCommandPreProcessor not firing
I'm trying a couple of different configurations for MyCommandPreProcessor, a generic and a non-generic but am stumped with either:
public class MyCommandPreProcessor<TRequest> : IRequestPreProcessor<TRequest>
{
public Task Process(TRequest request, CancellationToken cancellationToken)
{
Debug.WriteLine("***** MYCOMMAND PREPROCESSOR CALLED *****");
return Task.CompletedTask;
}
}
- OR -
public class MyCommandPreProcessor : IRequestPreProcessor<IRequest<ICommonResponse>>
{
public Task Process(TRequest request, CancellationToken cancellationToken)
{
Debug.WriteLine("***** MYCOMMAND PREPROCESSOR CALLED *****");
return Task.CompletedTask;
}
}
My Question
Any ideas on how I can register a pre-processor that will be restricted to only fire for IRequest<> types that are closed types of ICommand<>?
Project on GitHub
The entire minimal sample project can be viewed or cloned at https://github.com/jhoiby/MediatRPreProcessorTest
Autofac MediatR Config
A working config, with a single GenericRequestPreProcessor for all requests.
builder.RegisterAssemblyTypes(typeof(IMediator).GetTypeInfo().Assembly).AsImplementedInterfaces();
var mediatrOpenTypes = new[]
{
typeof(IRequestHandler<,>),
typeof(IRequestHandler<>),
typeof(INotificationHandler<>)
};
foreach (var mediatrOpenType in mediatrOpenTypes)
{
// Register all command handler in the same assembly as WriteLogMessageCommandHandler
builder
.RegisterAssemblyTypes(typeof(MyCommandHandler).GetTypeInfo().Assembly)
.AsClosedTypesOf(mediatrOpenType)
.AsImplementedInterfaces();
// Register all QueryHandlers in the same assembly as GetExternalLoginQueryHandler
builder
.RegisterAssemblyTypes(typeof(MyQueryHandler).GetTypeInfo().Assembly)
.AsClosedTypesOf(mediatrOpenType)
.AsImplementedInterfaces();
}
// Pipeline pre/post processors
builder.RegisterGeneric(typeof(RequestPostProcessorBehavior<,>)).As(typeof(IPipelineBehavior<,>));
builder.RegisterGeneric(typeof(RequestPreProcessorBehavior<,>)).As(typeof(IPipelineBehavior<,>));
builder.RegisterGeneric(typeof(GenericRequestPreProcessor<>)).As(typeof(IRequestPreProcessor<>));
// builder.RegisterGeneric(typeof(GenericRequestPostProcessor<,>)).As(typeof(IRequestPostProcessor<,>));
// builder.RegisterGeneric(typeof(GenericPipelineBehavior<,>)).As(typeof(IPipelineBehavior<,>));
builder.Register<SingleInstanceFactory>(ctx =>
{
var c = ctx.Resolve<IComponentContext>();
return t => c.Resolve(t);
});
builder.Register<MultiInstanceFactory>(ctx =>
{
var c = ctx.Resolve<IComponentContext>();
return t => (IEnumerable<object>)c.Resolve(typeof(IEnumerable<>).MakeGenericType(t));
});
MyCommandPreProcessor Class
I'm experimenting with both of these, generic and non-generic:
public class MyCommandPreProcessor<TRequest> : IRequestPreProcessor<TRequest>
{
public Task Process(TRequest request, CancellationToken cancellationToken)
{
Debug.WriteLine("***** MYCOMMAND PREPROCESSOR CALLED *****");
return Task.CompletedTask;
}
}
- AND -
public class MyCommandPreProcessor : IRequestPreProcessor<IRequest<ICommonResponse>>
{
public Task Process(TRequest request, CancellationToken cancellationToken)
{
Debug.WriteLine("***** MYCOMMAND PREPROCESSOR CALLED *****");
return Task.CompletedTask;
}
}
Inheritance Structures
// Requests
IMediatR.IRequest<TResponse>
<- IMessage<TResponse>
<- ICommand<TResponse>
<- concrete MyCommand : ICommand<CommonResponse>
<- IQuery<TResponse>
<- concrete MyQuery : IQuery<CommonResponse>
// Request Handlers
IMediatR.IRequestHandler<in TRequest,TResponse>
<- IMessageHandler<in TRequest,TResponse>
<- ICommandHandler<in TRequest,TResponse>
<- concrete MyCommandHandler : ICommandHandler<MyCommand,CommonResponse>
<- IQueryHandler<In TRequest,TResponse>
<- concrete MyQueryHandler : IQueryHandler<MyQuery,CommonResponse>
// CommonResponse - A POCO that returns result info
ICommonResponse
<- concrete CommonResponse
Commands
public interface IMessage<TResponse> : MediatR.IRequest<TResponse>
{
}
public interface ICommand<TResponse> : IMessage<TResponse>
{
}
public class MyCommand : ICommand<CommonResponse>
{
}
Command Handlers
public interface IMessageHandler<in TRequest, TResponse>
: MediatR.IRequestHandler<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
}
public interface ICommandHandler<in TRequest, TResponse>
: IMessageHandler<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
}
public class MyCommandHandler : ICommandHandler<MyCommand, CommonResponse>
{
public async Task<CommonResponse> Handle(
MyCommand request,
CancellationToken cancellationToken)
{
Debug.WriteLine(" ***** Command handler executing *****");
return
new CommonResponse(
succeeded: true,
data: "Command execution completed successfully.");
}
}
PreProcessor Injection Target (in the MediatR Pipeline Code)
The constructor that receives the injected IRequestPreProcessor<> is:
public RequestPreProcessorBehavior(IEnumerable<IRequestPreProcessor<TRequest>> preProcessors)
{
...
}
It can be seen on Github on line 17 of the file at:
https://github.com/jbogard/MediatR/blob/master/src/MediatR/Pipeline/RequestPreProcessorBehavior.cs
Thank you!
Upvotes: 5
Views: 9574
Reputation: 19
Just add the interface on command/query class and add the preprocessor for that interface. Please go through the below example
public interface IUser{}
public class AddUserCommand: IUser, IRequest<UserModel>{....
public class UpdateUserCommand: IUser, IRequest<UserModel>{....
public class GetUserQuery: IRequest<UserModel>{....
remember: here in above classes GetUserQuery class don't have IUser interface. Now lets create the pre-processor class which execute only for command/query which marked with IUser interface
public class UserCommandQueryPrepProcessor<T>: IRequestPreProcessor<T> where T: IUser
{.....
public Task Process(T request, CancellationToken token)
{ //enter your interface specific logic here
}
}
Note: above UserCommandQueryPrepProcessor class is a generic class with where IUser which executes only for class 'AddUserCommand' and 'UpdateUserCommand' inheriting IUser interface.
Upvotes: 0
Reputation: 353
I have the same exact scenario as you and I believe the issue is stemming from RequestPreProcessorBehavior<TRequest, TResponse>
not passing all types down to IRequestPreProcessor<TRequest>
.
You either:
request
in MyCommandPreProcessor<TRequest>
in every IRequestPreProcessor
:public Task Process(TRequest request, CancellationToken cancellationToken)
{
var isCommand = typeof(TRequest).GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICommand<>));
if (isCommand)
{
// Magic
}
}
TRequest
of IPipelineBehavior<TRequest, TResponse>
:public interface IRequestPreProcessor<in TRequest, TResponse> : IRequestPreProcessor<TRequest>
where TRequest : IRequest<TResponse>
{
}
public class MyRequestPreProcessorBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IEnumerable<IRequestPreProcessor<TRequest, TResponse>> _preProcessors;
public RequestPreProcessorBehavior(IEnumerable<IRequestPreProcessor<TRequest, TResponse>> 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);
}
}
With option 2, you would add constraints to any class that implemented IRequestPreProcessor<TRequest, TResponse>
for command / query specific pre-processors.
Upvotes: 0