-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Home
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
.
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.RegisterServicesFromAssembly(typeof(Program).Assembly);
});
This method registers the known MediatR types:
-
IMediator
as transient -
ISender
as transient -
IPublisher
as transient
For each assembly registered, the AddMediatR
method will scan those assemblies for MediatR types (excluding behaviors):
-
IRequestHandler<,>
concrete implementations as transient -
IRequestHandler<>
concrete implementations as transient -
INotificationHandler<>
concrete implementations as transient -
IStreamRequestHandler<>
concrete implementations as transient -
IRequestPreProcessor<>
concrete implementations as transient -
IRequestPostProcessor<,>
concrete implementations as transient -
IRequestExceptionHandler<,,>
concrete implementations as transient -
IRequestExceptionAction<,>)
concrete implementations as transient
MediatR has two kinds of messages it dispatches:
- Request/response messages, dispatched to a single handler
- Notification messages, dispatched to multiple handlers
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, implement the non-generic IRequest
interface and subsequent handler:
public class OneWay : IRequest { }
public class OneWayHandler : IRequestHandler<OneWay>
{
public Task Handle(OneWay request, CancellationToken cancellationToken)
{
// do work
return Task.CompletedTask;
}
}
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
Each request type has its own handler interface:
-
IRequestHandler<T, U>
- implement this and returnTask<U>
Then for requests without return values:
-
IRequestHandler<T>
- implement this and you will returnTask
.
To create a stream from a request, first implement the stream request and its response:
IStreamRequest<TResponse>
Stream request handlers are separate from the normal IRequestHandler
and require implementing:
IStreamRequestHandler<TRequest, TResponse>
Unlike normal request handlers that return a single TResponse
, a stream handler returns an IAsyncEnumerable<TResponse>
:
IAsyncEnumerable<TResponse> 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.
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());
MediatR (starting with version 12) supports custom publish strategies:
public interface INotificationPublisher
{
Task Publish(IEnumerable<NotificationHandlerExecutor> 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:
-
ForeachAwaitPublisher
- the default, existing implementation -
TaskWhenAllPublisher
- awaitsTask.WhenAll
for all notification handler tasks. Does NOT useTask.Run
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.
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.
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.
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.
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 fromTException
and were thrown from any requests that inheritTRequest
; -
IRequestExceptionHandler<in TRequest, TResponse>
- implement to handle all exceptions which were thrown from any requests that inheritTRequest
.
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 inheritsTRequest
; -
RequestExceptionHandler<TRequest, TResponse, TException>
- inherit this to synchronously handle any exception that inheritsTException
and were thrown from any requests that inheritsTRequest
; -
RequestExceptionHandler<TRequest, TResponse>
- inherit this to synchronously handle any exception which were thrown from any requests that inheritsTRequest
.
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.
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 fromTException
and were thrown from any requests that inheritTRequest
; -
IRequestExceptionAction<in TRequest>
- implement to execute action for any thrown exceptions which were thrown from any requests that inheritTRequest
.
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 inheritTRequest
; -
RequestExceptionAction<TRequest, TException>
- inherit this to synchronously execute action for thrown exceptions that inherits fromTException
and were thrown from any requests that inheritTRequest
; -
RequestExceptionAction<TRequest>
- inherit this to synchronously execute action for any thrown exceptions which were thrown from any requests that inheritTRequest
.
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.
Create a new handler / action that inherits the handler / action that you want to override, and save it in accordance with the priority rules.
- 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 severalcatch
and search handlers for all base exceptions separately; - Both support priority sorting;
- Both support overriding.