MediatR is a.NET tool class library that references the mediator pattern. It supports unicast or multicast message delivery within a process. It can be fully decoupled from message delivery and processing by using MediatR.

Before introducing MediatR, take a quick look at the mediator pattern. The mediator pattern mainly refers to the definition of a mediation object to schedule the interaction between a series of objects, objects do not need to explicitly reference each other, reducing the coupling. The following comparison diagram (difference between normal mode and intermediary mode) :

In fact, as you can see from the MediatR source code, it is not itself an implementation of the standard mediator pattern, so it is easy to understand here. I’ll look at how MediatR’s two messaging modes are used and then examine their implementation.

Create a the.net Core Web API project and install MediatR. Extensions. Microsoft. DependencyInjection NuGet package (including MediatR NuGet package). The service is then registered in ConfigureServices.

Services. AddMediatR(typeof(Startup));Copy the code

But by looking at the MediatR. Extensions. Microsoft. DependencyInjection notes AddMediatR contains specific what service registration and the registration of the object’s lifecycle, Basically all mediatr-related services have been registered with the IoC container with the above line of code.

Unicast message passing

Unicast messaging mainly involves IRequest (message type) and IRequestHandler (message processing) interfaces.

String specifies the return value type of the message handling method, as follows:

public class GenericRequest : IRequest<string>
{
  public string Name { get; set; }}Copy the code

GenericRequest specifies the type of message to be processed by this Handler. String specifies the return value type of the message processing method (consistent with the generic type specified by IRequest). In addition, the Handle method needs to be implemented as follows:

public class GenericRequestHandler : IRequestHandler<GenericRequest, string>
{
  public Task<string> Handle(GenericRequest request, CancellationToken cancellationToken)
  {
    return Task.FromResult($"This is {request.Name}"); }}Copy the code

Call test in Controller:

private readonly IMediator _mediator;

public MediatorController(IMediator mediator)
{
  _mediator = mediator;
}

[HttpGet]
public async Task<string> GenericRequest()
{
  var result = await _mediator.Send(new GenericRequest
  {
    Name = "GenericRequest"
  });
  return result;
}
Copy the code

There are also other request-types available for different code implementations, which are essentially extensions based on IRequest and IRequestHandler.

Multicast messaging

Multicast message delivery mainly involves two interfaces, INotification (message type) and INotificationHandler (message processing). In addition, multicast message delivery has no return value.

Define an implementation class for interface INotification as follows:

public class GenericNotification : INotification
{
  public string Name { get; set; }}Copy the code

GenericNotification specifies the type of message to be handled by this Handler. In addition, Handle should be implemented. Here we will define two NotificationHandler implementation classes for this message type, as follows:

public class GenericANotificationHandler : INotificationHandler<GenericNotification>
{
  public Task Handle(GenericNotification notification, CancellationToken cancellationToken)
  {
    Console.WriteLine($"A {notification.Name}");
    returnTask.CompletedTask; }}Copy the code
public class GenericBNotificationHandler : INotificationHandler<GenericNotification>
{
  public Task Handle(GenericNotification notification, CancellationToken cancellationToken)
  {
    Console.WriteLine($"B {notification.Name}");
    returnTask.CompletedTask; }}Copy the code

Call test in Controller:

[HttpGet]
public async Task GenericNotification()
{
  await _mediator.Publish(new GenericNotification
  {
    Name = "GenericNotification"
  });
}
Copy the code

The principle of analysis

It is recommended to read the source code, the code is small and the structure is clear, basic understanding is not difficult

From the previous introduction, it can be seen that the core interfaces for developers in MediatR are mainly IRequest&IRequestHandler, INotification&INotificationHandler, and IMediator.

Imediators are defined as follows:

public class Mediator : IMediator
{
    private readonly ServiceFactory _serviceFactory;
    private static readonly ConcurrentDictionary<Type, object> _requestHandlers = new ConcurrentDictionary<Type, object>();
    private static readonly ConcurrentDictionary<Type, NotificationHandlerWrapper> _notificationHandlers = new ConcurrentDictionary<Type, NotificationHandlerWrapper>();
}
Copy the code

We first define the ServiceFactory object, which represents the IoC container of the current application and is injected during application initialization. Such as MediatR. Extensions. Microsoft. DependencyInjection already contains the corresponding ServiceFactory registered. Since ServiceFactory is customizable, other frameworks with IoC container capabilities such as Autofac, Castle Windsor, DryIoc, etc.

In addition, _requestHandlers and _notificationHandlers hold HandlerWrapper objects corresponding to unicast and multicast message object types, respectively. The Main function of HandlerWrapper is to pass the ServiceFactory object, which ultimately fetches the corresponding message type Handler object from the IoC container.

MeidatR also supports pipelines that define message processing for unicast messages, such as implementing IRequestPreProcessor, IRequestPostProcessor to customize processing behavior before and after message processing. By implementing IRequestExceptionHandler, IRequestExceptionAction to customize handling behavior when exceptions occur, these implementation classes are also retrieved from the IoC container via ServiceFactory.

The following is the core code for unicast message processing:

public override Task<TResponse> Handle(IRequest<TResponse> request, CancellationToken cancellationToken, ServiceFactory serviceFactory)
{
  Task<TResponse> Handler() => GetHandler<IRequestHandler<TRequest, TResponse>>(serviceFactory).Handle((TRequest) request, cancellationToken);

  return serviceFactory
    .GetInstances<IPipelineBehavior<TRequest, TResponse>>()
    .Reverse()
    .Aggregate((RequestHandlerDelegate<TResponse>) Handler, (next, pipeline) => () => pipeline.Handle((TRequest)request, cancellationToken, next))();
}
Copy the code

The IPipelineBehavior is obtained from ServiceFactory, reversed by the Reverse method of Linq, and then executed by Aggregate. So the final execution order is RequestPreProcessorBehavior – Handler – RequestPostProcessorBehavior, the realization of this may be difficult to understand, the core is the use of Aggregate.

conclusion

The core of MediatR implementation is the decoupling of message passing implemented by IoC container by preserving the relationship between message request object and message processing object. In the practical application, MediatR multicast message passing can make the code implementation logic more concise, in addition, there are also more articles introduced through MediatR to achieve CQRS, EventBus and so on.