MediatR generic handlers
Asked Answered
S

2

9

I use a MediatR library in my Asp .Net core 6 app. And I have a the same logic for a group of requests, so I want to use the same handler for all of them. The problem 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 somebody knows a better way to solve this problem.

The example is shown here:

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 registration of MediatR is as follows:

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 get an exception:

Error constructing handler for request of type MediatR.IRequestHandler2[AT.Messenger.Credentials.Business.Requests.V1.Modeles.HandlerRequest1[System.Int32],MediatR.Unit]. Register your handlers with the container. See the samples in GitHub for examples.

The problem happens because MediatR can not register generic handler. I also tried to register handler as scoped or singleton service as I've seen that advice on the internet, but it does not help.

The only solution that I found - is inherit a new class from 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.

Shaquitashara answered 18/9, 2022 at 7:27 Comment(1)
This sounds similar to #60119390Gambado
L
12

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

Legislative answered 18/9, 2022 at 7:47 Comment(1)
This one services.AddTransient<IRequestHandler<HandlerRequest<int>, Unit>>, Handler<int>>(); works thank you.Shaquitashara
M
0

I have colleted a lot of information and figured out a solution to register any kind of generic Mediator Handlers.

I was stuck trying to register one generic implementation with another generic class inside INotificationHandler, like this:

public class DomainEventsDispatcher<TEvent> : INotificationHandler<DomainEventDispatch<TEvent>> where TEvent : DomainEvent

PS: I'll use the example above to explain below.

Obviously, your cannot simply use "services.AddMediatr(typeof(AppAssembly))" because using Mediator with generic types, Handlers are not loaded into DI on startup.

So, I decided to register each implementation of each type of TEvent, however, I did it by looping the assemblies that implemented TEvent type and building each part to be used on services.AddTransient.

  1. Looking for assemblies that implemented TEvent type;
  2. First variable: Build the generic used inside INotificationHandler;
  3. Second variable: Build the INotificationHandler with first variable;
  4. Third variable: Build the generic implementation with TEvent type;
  5. Returing the list ready to be injected;

Here is an extension I did to loop and build necessary types to register on DI:

public static class AssemblyExtensions
{
    public static List<(Type notificationHandler, Type implementation)> GetGenericNotificationHandlerForTypesOf(this Assembly assembly,
            Type AbstractClassToFilter, Type GenericUsedWithNotificationHandler, Type ImplementationType, Type NotificationHandlerGeneric)
    {
        List<(Type, Type)> list = new List<(Type, Type)>();

        foreach (Type item in from t in assembly.GetTypes()
                              where !t.IsAbstract && !t.IsInterface && t.BaseType == AbstractClassToFilter
                              select t)
        {
            var genericForNotificationHandler = GenericUsedWithNotificationHandler.MakeGenericType(item);
            var notificationHandler = NotificationHandlerGeneric.MakeGenericType(genericForNotificationHandler);
            var implementation = ImplementationType.MakeGenericType(item);

            list.Add((notificationHandler, implementation));
        }

        return list;
    }
}

Here is how to use (please take a look in the first example to understand the code below):

var registerPairs = typeof(DomainEvent).Assembly.GetGenericNotificationHandlerForTypesOf(typeof(DomainEvent), typeof(DomainEventDispatch<>), typeof(DomainEventsDispatcher<>), typeof(INotificationHandler<>));

registerPairs.ForEach(pair => services.AddTransient(pair.notificationHandler, pair.implementation));
Moser answered 12/2, 2023 at 6:4 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.