Skip to content
Jimmy Bogard edited this page Feb 17, 2015 · 69 revisions

A low-ambition library trying to solve a simple problem - decoupling the in-proc sending of messages from handling messages. Cross-platform, distributed as a portable class library, supporting .NET 4+, Silverlight 5, Windows 8, Windows Phone 8, Xamarin.Android, Xamarin.iOS, Xamarin.Unified, ASP.NET 5 and ASP.NET Core 5.

Setting up

Install via NuGet first: Install-Package MediatR

MediatR has no dependencies. You will need to configure two factory callbacks, one for building single instances and one for building multiple instances.

You'll need to configure three dependencies: first, the mediator itself. StructureMap example: cfg.For<IMediator>().Use<Mediator>(). The other two dependencies are factory delegates, SingleInstanceFactory and MultiInstanceFactory. The Mediator class is defined as:

public class Mediator : IMediator
{
    public Mediator(
        SingleInstanceFactory singleInstanceFactory,
        MultiInstanceFactory multiInstanceFactory)

The factory delegates are named delegates around a couple of generic factory methods:

public delegate object SingleInstanceFactory(Type serviceType);
public delegate IEnumerable<object> MultiInstanceFactory(Type serviceType);

StructureMap example: cfg.For<SingleInstanceFactory>().Use<SingleInstanceFactory>(ctx => t => ctx.GetInstance(t));. These two factory delegates are how MediatR builds instances of the request and notification handlers.

Finally, you'll need to register your handlers in your container of choice. StructureMap example:

new Container(cfg => cfg.Scan(scanner => {
    scanner.TheCallingAssembly();
    scanner.AddAllTypesOf(typeof(IRequestHandler<,>));
    scanner.AddAllTypesOf(typeof(INotificationHandler<>));
});

The full StructureMap example looks like:

var container = new Container(cfg =>
{
    cfg.Scan(scanner =>
    {
        scanner.AssemblyContainingType<Ping>(); // Our assembly with requests & handlers
        scanner.ConnectImplementationsToTypesClosing(typeof(IRequestHandler<,>));
        scanner.ConnectImplementationsToTypesClosing(typeof(IAsyncRequestHandler<,>));
        scanner.ConnectImplementationsToTypesClosing(typeof(INotificationHandler<>));
        scanner.ConnectImplementationsToTypesClosing(typeof(IAsyncNotificationHandler<>));
    });
    cfg.For<SingleInstanceFactory>().Use<SingleInstanceFactory>(ctx => t => ctx.GetInstance(t));
    cfg.For<MultiInstanceFactory>().Use<MultiInstanceFactory>(ctx => t => ctx.GetAllInstances(t));
    cfg.For<IMediator>().Use<Mediator>();
});

For more examples, check out the source code for working examples using:

  • Autofac
  • LightInject
  • Castle Windsor
  • Ninject
  • Simple Injector
  • StructureMap
  • Unity

These examples highlight all the features of MediatR including sync/async, request/response, pub/sub and more.

Basics

MediatR has two kinds of messages it dispatches:

  • Request/response messages, dispatched to a single handler
  • Notification messages, dispatched to multiple handlers

Request/response

The request/response interface handles both command and query scenarios. First, create a message:

public class Ping : IRequest<string> { }

Next, create handler:

public class PingHandler : IRequestHandler<Ping, string> {
    public string Handle(Ping request) {
        return "Pong";
    }
}

Finally, send a message through the mediator:

var response = mediator.Send(new Ping());
Debug.WriteLine(response); // "Pong"

In the rare case your message does not require a response, use the base RequestHandler class:

public class OneWay : IRequest { }
public class OneWayHandler : RequestHandler<OneWay> {
    protected override void HandleCore(OneWay request) {
        // Twiddle thumbs
    }
}

Publishing

For notifications, first create your notification message:

public class Ping : INotification { }

Next, create zero or more handlers for your notification:

public class Pong1 : INotificationHandler<Ping> {
    public void Handle(Ping notification) {
        Debug.WriteLine("Pong 1");
    }
}
public class Pong2 : INotificationHandler<Ping> {
    public void Handle(Ping notification) {
        Debug.WriteLine("Pong 2");
    }
}

Finally, publish your message via the mediator:

mediator.Publish(new Ping());

Polymorphic dispatch

Handler interfaces are co/contravariant:

public interface IRequestHandler<in TRequest, out TResponse>
    where TRequest : IRequest<TResponse> {
    TResponse Handle(TRequest message);
}
public interface INotificationHandler<in TNotification> {
    void Handle(TNotification notification);
}

Containers that support generic variance will dispatch accordingly. For example, you can have an INotificationHandler<INotification> to handle all notifications.

Async

Send/publish include async versions, with corresponding async-based interfaces for requests/responses/notifications:

public interface IAsyncRequest : IAsyncRequest<Unit> { }
public interface IAsyncRequest<out TResponse> { }
public interface IAsyncNotification { }

The IMediator interface includes additional methods for async:

await mediator.SendAsync(new PingAsync());

Your handlers can use the async/await keywords as long as the work is awaitable:

public class PingHandler : IAsyncRequestHandler<Ping, Pong> {
    public async Task<Pong> Handle(Ping request) {
        return await DoPong(); // Whatever DoPong does
    }
}

You will also need to register these handlers with your container of your choice, similar to the synchronous handlers shown above. Send, publish, and post-request handlers all support async. It is not possible to share messages or handlers between sync/async methods, as it would be unexpected behavior to wrap a synchronous handler in async if the underlying work did not support it. Async is opt-in from the message all the way down to the handler(s).

Clone this wiki locally