It can be intimidating and time consuming to explore a mature open source project. Often an attempt to find inspiration results in a surface level understanding of a few components with no real insight into the magic. In this series I hope to find projects of just the right size and complexity to “get in there” and learn something valuable. Let’s start by taking a look at MediatR.
I use MediatR in many projects because of its “simple to use” yet powerful implementation of the Mediator Pattern. A mediator sits between method callers and receivers creating a configurable layer of abstraction that determines how callers and receivers get wired up.
Let’s look at a quick example of a Controller calling some Application Layer code with MediatR.
public class CreateTicketCommand : IRequest<CreateTicketResponse>
{
public string Description { get; set; }
public string Department { get; set; }
public string Severity { get; set; }
}
public class TicketController: Controller
{
private readonly IMediator _mediator;
public TicketController(IMediator mediator)
{
_mediator = mediator;
}
[HttpPost]
public async Task<ActionResult> Create(CreateTicketModel model)
{
var command = new CreateTicketCommand()
{
Description = model.Description,
Department = model.Department,
Severity = model.Severity
};
var res = await _mediator.Send(command);
return RedirectToAction("Show", new { id = res.TicketId});
}
}
There are a few things to notice here.
- The Command defines the type of it’s response
IRequest<CreateTicketResponse>
- The
IMediator
is injected into the Controller - There is a single
Send
method on theIMediator
used to dispatch all command/request types - The Controller doesn’t know who is handling the command
Here’s a sample handler for CreateTicketCommand
public class CreateTicketHandler : IRequestHandler<CreateTicketCommand, CreateTicketResponse>
{
private readonly ApplicationDbContext _db;
public CreateTicketHandler(ApplicationDbContext db) => _db = db;
public async Task<CreateTicketResponse> Handle(CreateTicketCommand command, CancellationToken cancellationToken)
{
Ticket ticket = new Ticket(command.Description, command.Department, command.Severity);
_db.Tickets.Add(ticket);
await _db.SaveChangesAsync();
return new CreateTicketResponse() { TicektId = ticket.Id };
}
}
Notice:
- Handler doesn’t know who sent the command
- Handler specifies the the message type that it handles and the response type
IRequestHandler<CreateTicketCommand, CreateTicketResponse>
- Handler has constructor parameters that must be injected by the caller
We configure MediatR in our app’s startup with a single line
services.AddMediatR(typeof(Program));
Here’s what I’m thinking
-
I never registered any handlers explicitly; so I know that MediatR is scanning my assembly for handlers. This is pretty common and shouldn’t be much different than something like ASP.NET MVC finding your controllers, but I’d like to look under the hood a little.
-
The call to
_mediator.Send(command)
stands out as the interesting bit. Somehow this method can take any request type, find a concrete handler implementation for that request type, instantiate it, and call it. Let’s try to uncover the magic behind this!
Going to the Source
Let’s dig in. First download the source or browse online at https://github.com/jbogard/MediatR/tree/master/src/MediatR
> git clone https://github.com/jbogard/MediatR.git .
You’ll first notice a handful of simple interfaces and Mediator.cs
with the core class. Since we’re curious about it’s Send
method let’s take a look inside. The file is surprisingly small, and at first glance it looks like the Send
method is actually doing some of the real work.
public Mediator(ServiceFactory serviceFactory)
{
_serviceFactory = serviceFactory;
}
public Task<TResponse> Send<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken = default)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
var requestType = request.GetType();
var handler = (RequestHandlerWrapper<TResponse>)_requestHandlers.GetOrAdd(requestType,
t => Activator.CreateInstance(typeof(RequestHandlerWrapperImpl<,>).MakeGenericType(requestType, typeof(TResponse))));
return handler.Handle(request, cancellationToken, _serviceFactory);
}
First things first, above I made a call using Send(command)
, but the send method here is generic Send<TResponse>(IRequest<TResponse> request)
. It turns out that the compiler can infer the type argument; so you can omit it. Our call above looks much prettier than Send<CreateTicketResponse>(command)
(generics methods). Moving on…
I’m starting to get excited because the Send
method is basically a fat one-liner, yet it appears to be instantiating a handler and calling it (which is all a mediator really does). Let’s dig a little deeper.
var handler = (RequestHandlerWrapper<TResponse>)_requestHandlers.GetOrAdd(requestType,
t => Activator.CreateInstance(typeof(RequestHandlerWrapperImpl<,>).MakeGenericType(requestType, typeof(TResponse))));
The outer _requestHandlers.GetOrAdd
is just checking if a handler already exists before creating a new one. The real magic seems to be:
Activator.CreateInstance(typeof(RequestHandlerWrapperImpl<,>).MakeGenericType(requestType, typeof(TResponse)))
We’re getting close to something. We’re taking a RequestHandlerWrapperImpl<,>
calling .MakeGenericType(requestType, typeof(TResponse))
on it then instantiating the result, which is apparently assignable to RequestHandlerWrapper<TResponse>
. Let’s plug in our command type above to see what it looks like.
typeof(RequestHandlerWrapperImpl<,>).MakeGenericType(typeof(CreateTicketCommand), typeof(CreateTicketResponse))
This gives us a RequestHandlerWrapperImpl<CreateTicketCommand,CreateTicketResponse>
which is assignable to RequestHandlerWrapper<CreateTicketResponse>
. For now let’s think of “RequestHandlerWrapper” as some kind of wrapper around our actual handler (CreateTicketHandler
). We know that instantiating our actual handler will require Dependency Injection to resolve the dependencies (like ApplicationDbContext); so perhaps the wrapper is hiding these details.
But whats up with the cast from RequestHandlerWrapperImpl
down to the less generic RequestHandlerWrapper
? Believe it or not, this seemingly benign cast is part of the real magic. The bigger concept at play here is a technique for allowing non-generic code to call into generic code. It’s a little hard to see here because the return type is still generic, but notice how the TRequest
goes away when casting RequestHandlerWrapperImpl<TRequest,TResponse>
to RequestHandlerWrapper<TResponse>
.
Lets take a look inside:
internal abstract class RequestHandlerWrapper<TResponse> : RequestHandlerBase
{
public abstract Task<TResponse> Handle(IRequest<TResponse> request, CancellationToken cancellationToken,
ServiceFactory serviceFactory);
}
internal class RequestHandlerWrapperImpl<TRequest, TResponse> : RequestHandlerWrapper<TResponse>
where TRequest : IRequest<TResponse>
{
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))();
}
Cool. So RequestHandlerWrapperImpl<TRequest, TResponse>
implements the abstract RequestHandlerWrapper<TResponse>
. So we have the generic implementation extending the non-generic (with respect to TRequest). Ultimately, RequestHandlerWrapper<TResponse>
is providing a single non-generic handler interface that we can use to make calls into all the generic implementations for each request type. Let’s see how the generic version accomplishes this:
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))();
}
MediatR has some cool features around pipelines, but a basic mediator doesn’t need any of that. Let’s focus on how the RequestHandlerWrapperImpl<TRequest, TResponse>
does it’s job of calling our CreateTicketHandler
with this line:
Task<TResponse> Handler() => GetHandler<IRequestHandler<TRequest, TResponse>>(serviceFactory).Handle((TRequest) request, cancellationToken);
Let’s break this down
- First we call
GetHandler()
on our baseRequestHandlerBase
class. This should return our actual handler (CreateTicketHandler
). We’ll take a look inside a little later
GetHandler<IRequestHandler<TRequest, TResponse>>(serviceFactory)
- Then we call the
.Handle()
method on the handler (CreateTicketHandler
). We must cast theIRequest<TResponse>
toTRequest
to complete the bridge from the non-generic to generic.
.Handle((TRequest) request, cancellationToken)
- With our command it would look like this
//request is an IRequest<CreateTicketResponse> from the non-generic
.Handle((CreateTicketCommand) request, cancellationToken)
- Now instead of immediately running the
Handle()
method above, we’re using a lambda to create Task that we can pass around and call later
Task<TResponse> Handler() =>
At this point awaiting the Handler would wrap up our dispatch to CreateTicketHandler
. Let’s back up one step and see how the instantiation of our handler with Dependency Injection went down in RequestHandlerBase
.
protected static THandler GetHandler<THandler>(ServiceFactory factory)
{
THandler handler;
try
{
handler = factory.GetInstance<THandler>();
}
Interesting, I really expected more code. Let’s take a look at ServiceFactory
to see what’s up.
public delegate object ServiceFactory(Type serviceType);
public static class ServiceFactoryExtensions
{
public static T GetInstance<T>(this ServiceFactory factory)
=> (T) factory(typeof(T));
public static IEnumerable<T> GetInstances<T>(this ServiceFactory factory)
=> (IEnumerable<T>) factory(typeof(IEnumerable<T>));
}
Mind Blown! ServiceFactory
is just a delegate. As a bonus the method GetInstance<THandler>
is an extension method on the delegate. That’s right C# supports extension methods on delegates. Essentially, ServiceFactory
is a single method wrapper around our Dependency Injection container. The only burden ServiceFactory
puts on the underlying container is to have a method that takes in a Type
parameter and returns an instance of that type. Using a couple of extension methods we overlay a nicer generic interface T GetInstance<T>
and IEnumerable<T> GetInstances<T>
. You don’t see this kind of expressiveness in strongly-typed languages every day.
MediatR's support for all the different dependency injection frameworks boils down to a simple delegate. Ironically, if you look into one of the Dependency Injection integrations like MediatR.Extensions.Microsoft.DependencyInjection, it has about as much code for registering all the bells and whistles for handlers as the core of MediatR itself.