I use a MediatR library in my Asp .Net core 6 app. And I have a the saime logic for a group of requests, so I whant to use the same handler for all of them. The proplem is not new, but the solutions that I found did not help me. I have my own solution to solve this problem, but I do not like it and hope somedoy knows a better way to solve this problem. The example below.
public class HandlerRequest<T> : IRequest
{
public T Data { get; set; }
}
public class Handler<T> : IRequestHandler<HandlerRequest<T>>
{
public Task<Unit> Handle(HandlerRequest<T> request, CancellationToken cancellationToken)
{
// the logic is here
return Task.FromResult(new Unit());
}
}
The registrating of MediatR is following:
builder.Services.AddMediatR(typeof(BusinessLayer));
When I try to call my mediatr for example:
await _mediator.Send(new HandlerRequest<int>{Data = 5});
await _mediator.Send(new HandlerRequest<string>{Data = "TEST"});
I have an exception:
Error constructing handler for request of type MediatR.IRequestHandler
2[AT.Messenger.Credentials.Business.Requests.V1.Modeles.HandlerRequest
1[System.Int32],MediatR.Unit]. Register your handlers with the container. See the samples in GitHub for examples.
The problem happens becose MediatR can not register generic handler. I also tried to rigister handler as Scoped or singletone service as peple advice in the internet, but it does not help.
The only solution that I found - is inherit a new class fom my handler :
public class HandlerInt : Handler<int>
{
}
public class HandlerString : Handler<string>
{
}
If use this approach, all works, but I don`t like this solution. If anybody know better way to solve this problem give me an advice please.
CodePudding user response:
You have the following options, first, register what you will need explicitly like this.
services.AddTransient<IRequestHandler<HandlerRequest<int>, Unit>>, Handler<int>>();
//so on and so forth
This way you have registered the handlers for known types.
If you have open-bound generic, you can look into my PR that gives you a
services.RegisterGenericMediatRHandlers(typeof(GenericHandlerBase).Assembly);
to automatically register classes inheriting from the constrained interface by using the method provided above.
Lastly, you can opt to use Autofac and register all your handlers like this:
var mediatrOpenTypes = new[]
{
typeof(IRequestHandler<,>),
typeof(IRequestExceptionHandler<,,>),
typeof(IRequestExceptionAction<,>),
typeof(INotificationHandler<>),
};
foreach (var mediatrOpenType in mediatrOpenTypes)
{
builder
.RegisterAssemblyTypes(_assemblies.ToArray())
.AsClosedTypesOf(mediatrOpenType)
.AsImplementedInterfaces();
}
//also by simply
//builder.RegisterGeneric(typeof(Handler<>)).AsImplementedInterfaces();
This will also register all the generic handlers and you won't even have to register it explicitly inside the DI.
btw The method you used to handle generic registration is the way described in the docs, to quote
If you have an open generic not listed above, you'll need to register it explicitly. For example, if you have an open generic request handler, register the open generic types explicitly:
services.AddTransient(typeof(IRequestHandler<,>), typeof(GenericHandlerBase<,>));
This won't work with generic constraints, so you're better off creating an abstract base class and concrete closed generic classes that fill in the right types.
Source: docs link