Skip to content
Pavel edited this page Dec 20, 2019 · 46 revisions

MediatR is a low-ambition library trying to solve a simple problem — decoupling the in-process sending of messages from handling messages. Cross-platform, supporting .NET Framework 4.6.1 and netstandard2.0.

Setup

Install the package 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 will need to configure two dependencies: first, the mediator itself. 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);

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.

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

ASP.NET Core

If you're using ASP.NET Core then you can skip the 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/post-processors in a given assembly.

StructureMap

Setup Mediator itself: cfg.For<IMediator>().Use<Mediator>().

Setup the factory delegate: cfg.For<ServiceFactory>().Use<ServiceFactory>(ctx => ctx.GetInstance);. This factory delegate is how MediatR builds instances of the request and notification handlers.

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

The full 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 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 & notification handlers
builder.Register<ServiceFactory>(context =>
{
    var c = context.Resolve<IComponentContext>();
    return t => c.Resolve(t);
});

// 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

Other

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

  • Autofac
  • Castle Windsor
  • DryIoc
  • Lamar
  • LightInject
  • 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 a 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).

Notifications

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());

Publish strategies

The default implementation of Publish loops through the notification handlers and awaits each one. This ensures each handler is run after one another.

Depending on your use-case for publishing notifications, you might need a different strategy for handling the notifications. Maybe you want to publish all notifications in parallel, or wrap each notification handler with your own exception handling logic.

A few example implementations can be found in MediatR.Examples.PublishStrategies. This shows four different strategies documented on the PublishStrategy enum.

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 asynchronous from the IMediator side, with corresponding synchronous and asynchronous-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 the IoC container of your choice, similar to the synchronous handlers shown above.

Exceptions handling

Exception handler pipeline step

Exception handler implemented by using IPipelineBehavior concept. It requires to add the RequestExceptionProcessorBehavior to the request execution Pipeline.

Handlers types

There are two flavors of exception handlers in the MediatR - ones that specify exact exception, and ones that do not:

  • IRequestExceptionHandler<in TRequest, TResponse, TException> - implement to handle exceptions that inherits from TException and were thrown from any requests that inherit TRequest;
  • IRequestExceptionHandler<in TRequest, TResponse> - implement to handle all exceptions which were thrown from any requests that inherit TRequest.

To simplify and generalize the exceptions handling, IRequestExceptionHandler<in TRequest, TResponse> inherits IRequestExceptionHandler<TRequest, TResponse, Exception>

Several abstractions exists in the MediatR to simplify exception handler creation:

  • AsyncRequestExceptionHandler<TRequest, TResponse> - inherit this to asynchronously handle any exception which were thrown from any requests that inherits TRequest;
  • RequestExceptionHandler<TRequest, TResponse, TException> - inherit this to synchronously handle any exception that inherits TException and were thrown from any requests that inherits TRequest;
  • RequestExceptionHandler<TRequest, TResponse> - inherit this to synchronously handle any exception which were thrown from any requests that inherits TRequest.

Exception action pipeline step

Exception action implemented by using IPipelineBehavior concept. It requires to add the RequestExceptionActionProcessorBehavior to the request execution Pipeline. If place RequestExceptionActionProcessorBehavior before RequestExceptionProcessorBehavior, actions will be called only for unhandled exceptions.

Action types

There are two flavors of exception actions in the MediatR - ones that specify exact exception, and ones that do not:

  • IRequestExceptionAction<in TRequest, in TException> - implement to execute action for thrown exceptions that inherits from TException and were thrown from any requests that inherit TRequest;
  • IRequestExceptionAction<in TRequest> - implement to execute action for any thrown exceptions which were thrown from any requests that inherit TRequest.

To generalize the exceptions actions, IRequestExceptionAction<in TRequest> inherits IRequestExceptionAction<TRequest, Exception>

Several abstractions exists in the MediatR to simplify exception action creation:

  • AsyncRequestExceptionAction<TRequest> - inherit this to asynchronously execute action for any thrown exceptions which were thrown from any requests that inherit TRequest;
  • RequestExceptionAction<TRequest, TException> - inherit this to synchronously execute action for thrown exceptions that inherits from TException and were thrown from any requests that inherit TRequest;
  • RequestExceptionAction<TRequest> - inherit this to synchronously execute action for any thrown exceptions which were thrown from any requests that inherit TRequest.

Handlers and actions priority execution

All available handlers/actions will be sorted by applying next rules:

  • The handler/action has a higher priority if it belongs to the current assembly (same assembly with request) and the other is not. If none of the objects belong to the current assembly, they can be considered equal. If both objects belong to the current assembly, they can't be compared only by this criterion - compare by next rule;
  • The handler/action has a higher priority if it belongs to the current/child request namespace and the other is not. If both objects belong to the current/child request namespace, they can be considered equal. If none of the objects belong to the current/child request namespace, they can't be compared by this criterion - compare by next rule;
  • The handler/action has a higher priority if it namespace is part of the current location (request namespace) and the other is not. If both objects are part of the current location, the closest has higher priority. If none of the objects are part of the current location, they can be considered equal.

Override handler/action

Create a new handler / action that inherits the handler / action that you want to override, and save it in accordance with the priority rules.

Exception handler vs Exception action

  • All actions will be performed for the thrown exception. However, only one handler can handle the thrown exception;
  • The exception will be re-thrown after all actions are completed. But the exception can be handled by the exception handler, and the result will be returned to the caller;
  • The actions execution process faster. Because the exception handler works like try / catch block with several catch and search handlers for all base exceptions separately;
  • Both support priority sorting;
  • Both support overriding.
You can’t perform that action at this time.