Home (original) (raw)

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 netstandard2.0.

Setup

Install the package via NuGet first:Install-Package MediatR

MediatR directly references Microsoft.Extensions.DependencyInjection.Abstractions leveraging IServiceProvider. Typical usage is to use IServiceCollection directly:

services.AddMediatR(cfg => { cfg.LicenseKey = ""; cfg.RegisterServicesFromAssembly(typeof(Program).Assembly); });

This method registers the known MediatR types:

For each assembly registered, the AddMediatR method will scan those assemblies for MediatR types (excluding behaviors):

Behaviors and pre/post processors must be registered explicitly through the AddXyz methods.

Basics

MediatR has two kinds of messages it dispatches:

Request/response

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

public class Ping : IRequest { }

Next, create a handler:

public class PingHandler : IRequestHandler<Ping, string> { public Task 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, implement the non-generic IRequest interface and subsequent handler:

public class OneWay : IRequest { } public class OneWayHandler : IRequestHandler { public Task Handle(OneWay request, CancellationToken cancellationToken) { // do work return Task.CompletedTask; } }

Request types

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

Each request type has its own handler interface:

Then for requests without return values:

Streams and AsyncEnumerables

To create a stream from a request, first implement the stream request and its response:

Stream request handlers are separate from the normal IRequestHandler and require implementing:

Unlike normal request handlers that return a single TResponse, a stream handler returns an IAsyncEnumerable<TResponse>:

IAsyncEnumerable Handle(TRequest request, CancellationToken cancellationToken);

To create a stream request handler, create a class that implements IStreamRequestHandler<TRequest, TResponse> and implement the above Handle method.

Finally, send a stream message through the mediator:

CancellationTokenSource cts = new();
int count = 10;
await foreach (var item in mediator.CreateStream<int>(new CounterStreamRequest(), cts.Token))
{
    count--;
    if (count == 0)
    {
        cts.Cancel();
    }
    Debug.WriteLine(item);
}

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

public class Pong2 : INotificationHandler { 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());

Custom notification publishers

MediatR (starting with version 12) supports custom publish strategies:

public interface INotificationPublisher { Task Publish(IEnumerable handlerExecutors, INotification notification, CancellationToken cancellationToken); }

Custom publish strategies are injected into the Mediator class, and are registered with AddMediatR:

services.AddMediatR(cfg => {
    cfg.RegisterServicesFromAssemblyContaining<Program>();
    cfg.NotificationPublisher = new MyCustomPublisher(); // this will be singleton
    cfg.NotificationPublisherType = typeof(MyCustomPublisher); // this will be the ServiceLifetime
});

There are two built-in notification publishers:

Custom notification publishers can also use the handler instance to do custom logic like ordering, skipping handlers, etc. The handler instance is on the NotificationHandlerExecutor class, as well as the delegate to call the handler instance itself.

Auto register Generic Requests and Handlers

Configuration

///

/// Configure the maximum number of type parameters that a generic request handler can have. To Disable this constraint, set the value to 0. /// public int MaxGenericTypeParameters { get; set; } = 10;

///

/// Configure the maximum number of types that can close a generic request type parameter constraint. To Disable this constraint, set the value to 0. /// public int MaxTypesClosing { get; set; } = 100;

///

/// Configure the Maximum Amount of Generic RequestHandler Types MediatR will try to register. To Disable this constraint, set the value to 0. /// public int MaxGenericTypeRegistrations { get; set; } = 125000;

///

/// Configure the Timeout in Milliseconds that the GenericHandler Registration Process will exit with error. To Disable this constraint, set the value to 0. /// public int RegistrationTimeout { get; set; } = 15000;

///

/// Flag that controlls whether MediatR will attempt to register handlers that containg generic type parameters. /// public bool RegisterGenericHandlers { get; set; } = false;

Implementation

Requests can be made generic such that work done to or for specific type arguments can be written once towards an abstraction.

//you can also configure other values shown above here as well builder.Services.AddMediatR(cfg => { cfg.RegisterGenericHandlers = true; cfg.RegisterServicesFromAssembly(typeof(GenericPing<>).Assembly); });

public interface IPong { string? Message { get; } }

public class Pong : IPong { public string? Message { get; set; } }

//generic request definition public class GenericPing : IRequest where T : class, IPong { public T? Pong { get; set; } }

//generic request handler public class GenericPingHandler : IRequestHandler<GenericPing, T> where T : class, IPong { public Task Handle(GenericPing request, CancellationToken cancellationToken) => Task.FromResult(request.Pong!); }

//usage var pong = await _mediator.Send(new GenericPing{ Pong = new() { Message = "Ping Pong" } }); Console.WriteLine(pong.Message); //would output "Ping Pong"

Notes

Polymorphic dispatch

Handler interfaces are contravariant:

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

public interface INotificationHandler { 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 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. This behavior is not registered unless AddMediatR finds request exception behaviors.

namespace MediatR.Pipeline;

///

/// Behavior for executing all instances /// after an exception is thrown by the following pipeline steps /// /// Request type /// Response type public class RequestExceptionProcessorBehavior<TRequest, TResponse, TException> : IRequestExceptionHandler<TRequest, TResponse, TException> where TRequest : notnull {

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.

namespace MediatR.Pipeline;

///

/// Behavior for executing all instances /// after an exception is thrown by the following pipeline steps /// /// Request type /// Response type public class RequestExceptionActionProcessorBehavior<TRequest, TResponse> : IRequestExceptionAction<TRequest, TException> where TRequest : notnull {

Handlers and actions priority execution

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

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