Jimmy Bogard edited this page Jun 5, 2018 · 35 revisions

A low-ambition library trying to solve a simple problem - decoupling the in-proc sending of messages from handling messages. Cross-platform, supporting .NET 4.5, netstandard1.3, and netstandard2.0.

Setting up

Install via NuGet first: Install-Package MediatR

MediatR has no dependencies. You will need to configure a single factory delegate, used to instantiate all handlers, pipeline behaviors, and pre/post-processors.

You'll need to configure two dependencies: first, the mediator itself. StructureMap example: cfg.For<IMediator>().Use<Mediator>(). The other dependency is the factory delegate, ServiceFactory. The Mediator class is defined as:

public class Mediator : IMediator
{
    public Mediator(
        ServiceFactory serviceFactory)

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

public delegate object ServiceFactory(Type serviceType);

StructureMap example: cfg.For<ServiceFactory>().Use<ServiceFactory>(ctx => ctx.GetInstance);. This factory delegate is 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<>));
});

Declare whatever flavor of handler you need - sync, async or cancellable async. From the IMediator side, the interface is async-only, designed for modern hosts.

StructureMap

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(INotificationHandler<>));
    });
    cfg.For<ServiceFactory>().Use<ServiceFactory>(ctx => ctx.GetInstance);
    cfg.For<IMediator>().Use<Mediator>();
});

Autofac

The full Autofac example looks like:

// uncomment to enable polymorphic dispatching of requests, but note that
// this will conflict with generic pipeline behaviors
// builder.RegisterSource(new ContravariantRegistrationSource());

// mediator itself
builder
  .RegisterType<Mediator>()
  .As<IMediator>()
  .InstancePerLifetimeScope();

// request handlers
builder
  .Register<SingleInstanceFactory>(ctx => {
    var c = ctx.Resolve<IComponentContext>();
    return t => c.TryResolve(t, out var o) ? o : null;
  })
  .InstancePerLifetimeScope();

// notification handlers
builder
  .Register<MultiInstanceFactory>(ctx => {
    var c = ctx.Resolve<IComponentContext>();
    return t => (IEnumerable<object>)c.Resolve(typeof(IEnumerable<>).MakeGenericType(t));
  })
  .InstancePerLifetimeScope();

// finally register our custom code (individually, or via assembly scanning)
// - requests & handlers as transient, i.e. InstancePerDependency()
// - pre/post-processors as scoped/per-request, i.e. InstancePerLifetimeScope()
// - behaviors as transient, i.e. InstancePerDependency()
builder.RegisterAssemblyTypes(typeof(MyType).GetTypeInfo().Assembly).AsImplementedInterfaces(); // via assembly scan
//builder.RegisterType<MyHandler>().AsImplementedInterfaces().InstancePerDependency();          // or individually

ASP.NET Core

If you're using ASP.NET Core then you can skip the above configuration and use MediatR's MediatR.Extensions.Microsoft.DependencyInjection package which includes a IServiceCollection.AddMediatR(Assembly) extension method, allowing you to register all Handlers and Pre/PostProcessors in a given assembly.

Other

For more examples, check out the samples for working examples using:

  • Autofac
  • LightInject
  • Castle Windsor
  • DryIoc
  • Ninject
  • Simple Injector
  • StructureMap
  • Lamar
  • 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 Task<string> Handle(Ping request, CancellationToken cancellationToken) {
        return Task.FromResult("Pong");
    }
}

Finally, send a message through the mediator:

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

In the case your message does not require a response, use the AsyncRequestHandler<TRequest> base class:

public class OneWay : IRequest { }
public class OneWayHandlerWithBaseClass : AsyncRequestHandler<OneWay> {
    protected override Task Handle(OneWay request, CancellationToken cancellationToken) {
        // Twiddle thumbs
    }
}

Or if the request is completely synchronous, inherit from the base RequestHandler class:

public class SyncHandler : RequestHandler<Ping, string> {
    protected override string Handle(Ping request) {
        return "Pong";
    }
}

Request types

There are two flavors of requests in MediatR - ones that return a value, and ones that do not:

  • IRequest<T> - the request returns a value
  • IRequest - the request does not return a value

To simplify the execution pipeline, IRequest inherits IRequest<Unit> where Unit represents a terminal/ignored return type.

Each request type has its own handler interface, as well as some helper base classes:

  • IRequestHandler<T, U> - implement this and return Task<U>
  • RequestHandler<T, U> - inherit this and return U

Then for requests without return values:

  • IRequestHandler<T> - implement this and you will return Task<Unit>.
  • AsyncRequestHandler<T> - inherit this and you will return Task.
  • RequestHandler<T> - inherit this and you will return nothing (void).

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 Task Handle(Ping notification, CancellationToken cancellationToken) {
        Debug.WriteLine("Pong 1");
        return Task.CompletedTask;
    }
}
public class Pong2 : INotificationHandler<Ping> {
    public Task Handle(Ping notification, CancellationToken cancellationToken) {
        Debug.WriteLine("Pong 2");
        return Task.CompletedTask;
    }
}

Finally, publish your message via the mediator:

await mediator.Publish(new Ping());

Polymorphic dispatch

Handler interfaces are contravariant:

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

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

Async

Send/publish are async from the IMediator side, with corresponding sync and async-based interfaces/base classes for requests/responses/notification handlers.

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

public class PingHandler : IRequestHandler<Ping, Pong> {
    public async Task<Pong> Handle(Ping request, CancellationToken cancellationToken) {
        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.

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.