jbogard / MediatR

Simple, unambitious mediator implementation in .NET
Apache License 2.0
10.91k stars 1.16k forks source link

[Feature Request] Allow Configuration to set Custom Wrapper Implementations or extend DI to handle injection. #949

Closed JaronrH closed 11 months ago

JaronrH commented 11 months ago

I ran into a case where I needed to extend how DI does Notification Handler lookups. In my case, I wanted handlers based on an implemented interface to be used. i.e. If the Notification implemented IMyNotification (which is based on INotification), I wanted to have a handler for INotificationHandler which would handle any notification that implements IMyNotification.

In order to add my fix, I needed to make my own IMediator where the only change is that it is now using my implementation of NotificationHandlerWrapperImpl<>. So, my request would be to allow this to be changed via configuration or have DI inject it. I would rather not have to duplicate the entire Mediator implementation just to add logic that exists in another class!

As far as the changes to NotificationHandlerWrapperImpl<>, here is what I did: 1) Get all interfaces implemented by TNotification which are based on INotification. 2) Generate Generic Types for INotificationHandler<> with these interfaces. 3) Use DI get get all handlers that implement the handlers. 4) Create NotificationHandlerExecutor's which use reflection to call the appropriate handlers found in DI. 5) Concat these handlers to the default handler.


public class NotificationHandlerWrapperImpl<TNotification> : NotificationHandlerWrapper
    where TNotification : INotification
{
    public override Task Handle(INotification notification, IServiceProvider serviceFactory,
        Func<IEnumerable<NotificationHandlerExecutor>, INotification, CancellationToken, Task> publish,
        CancellationToken cancellationToken)
    {
        // Custom Code to get all Notification Handlers based on Interfaces the Notification Type Implements.
        var type = typeof(TNotification);
        var interfaceHandlers = type
            .GetInterfaces()
            .Where(i => i.IsAssignableTo(typeof(INotification)))
            .Select(i => typeof(INotificationHandler<>).MakeGenericType(i))
            .SelectMany(i => serviceFactory.GetServices(i).Select(s => (Type: i, Service: s)))
            .Select(static x => new NotificationHandlerExecutor(x.Service!, (theNotification, theToken) => (Task)x.Type.GetMethod("Handle")!.Invoke(x.Service, new object[] { theNotification, theToken })!))
            .ToArray();

        // Original Handlers Method
        var handlers = serviceFactory
            .GetServices<INotificationHandler<TNotification>>()
            .Select(static x => new NotificationHandlerExecutor(x, (theNotification, theToken) => x.Handle((TNotification)theNotification, theToken)));

        // Return Task call Publish
        return publish(handlers.Concat(interfaceHandlers), notification, cancellationToken);
    }
}
jbogard commented 11 months ago

You can achieve the same thing using generic constraints:

public class MyNotificationHandler<TNotification> : INotificationHandler<TNotification>
    where TNotification : IMyNotification

This could also be addressed at the registration side, but you'd have to provide more details OR use a better/more feature rich container.

JaronrH commented 11 months ago

Using better DI solved the issue - I'm closing this request now! (also, the implementation above had a bug where the same handler could appear multiple times)