Search code examples
c#autofacmediatr

Adapt/Wrap MediatR notifications in existing framework


TL;DR How can I adapt an existing set of events/notifications and related handlers so they can be used by MediatR without having to implement extra handlers and notifications for each existing type?

Long version: We currently have a system for dispatching events, where we use a bus implementation to do the actual transport.

I am currently experimenting with an In-process variant. So similar to MediatR's INotification we have a marker interface IEvent Likewise similar to the INotificationHandler<in INotification> of MediatR we have an IEventHandler<in IEvent>

We use Autofac to register our EventHandlers, and then we have some reflection stuff going on when actually consuming things from the bus.

What I would like to do, but cannot get my head around, is to implement some sort of general adapter/wrapper so that I can keep my existing Events and EventHandlers, and use those when dispatching notifications from MediatR, basically reducing the amount of changes for my existing code to revolve around setting up the container.

In terms of code I want to have the following class behave as if it was using the MediatR interfaces.

public class MyHandler : IEventHandler<SampleEvent>
{
    public Task Handle(SampleEvent input) => Task.CompletedTask;
}

Any suggestions?


Solution

  • Current situation

    So right now your code looks like this:

    public class SampleEvent : IEvent
    {
    }
    
    public class SampleEventHandler : IEventHandler<SampleEvent>
    {
        public Task Handle(SampleEvent input) => Task.CompletedTask;
    }
    

    Making events compatible with MediatR

    We need to have events that MediatR recognises, which means they need to implement INotification.

    The first way of doing this is to make your IEvent interface implement INotification. The great thing about this is there's very little code change, and it makes all your current and new events compatible with MediatR. The potentially not-so-great thing is that the assembly in which your current implementations of IEvent live need to take a dependency on MediatR.

    public interface IEvent : INotification
    {
    }
    

    If that's not feasible, the second way I see of doing this is creating new, MediatR-specific classes that inherit from your existing and also implement INotification. This means you need to create one adapter class per existing class, but you free your existing project from a MediatR dependency.

    // Lives in AssemblyA
    public class ExistingEvent : IEvent
    {
    }
    
    // Lives in AssemblyB that has a dependency on both
    // AssemblyA and MediatR
    public class MediatrExistingEvent : ExistingEvent, INotification
    {
    }
    

    Wiring the handlers

    Whichever way you went in the previous step, you're now in a state where you have classes that implement both IEvent and INotification, and you have handlers that implement IEventHandler<in T> where T : IEvent.

    We could create an adapter class that would satisfy MediatR's API and delegate the work to your existing handlers:

    public class MediatrAdapterHandler<T> : INotificationHandler<T>
        where T : IEvent, INotification
    {
        private readonly IEventHandler<T> _inner;
    
        public MediatrAdapterHandler(IEventHandler<T> inner)
        {
            _inner = inner;
        }
    
        public Task Handle(T notification) => _inner.Handle(notification);
    }
    

    The final thing is to register this class in the Autofac container. Given your existing handlers are registered as IEventHandler<T>, it's done very easily:

    builder
        .RegisterGeneric(typeof(MediatrAdapterHandler<>))
        .As(typeof(INotificationHandler<>));
    

    Every time you now ask the container an instance of INotificationHandler<T>, it will create an instance of MediatrAdapterHandler<T> in which your original implementation of IEventHandler<T> will be injected.