Reputation: 1724
I'm learning MediatR and I have a problem connecting the Controller to the Handler.
Controller:
[HttpGet]
public async Task<List<PersonModel>> Get()
{
return await _mediator.Send(new GetPersonListQuery());
}
My understanding is that the Controller will invoke the Handler using _mediator.Send()
. However, the argument for Send is not the Handler but the Command
/ Query
.
In Visual Studio, I checked how many reference there are to the Handler and it says Zero. So how does the MediatR know which particular handler to call?
Upvotes: 9
Views: 10172
Reputation: 387527
MediatR makes heavy use of generic types in order for its mechanism to work. As you probably noticed from using it, you are required to use certain interfaces in the right way in order for your requests and handlers to be usable for MediatR.
Let’s take your example to explain how this all works:
await _mediator.Send(new GetPersonListQuery());
In order for GetPersonListQuery
to be accepted, it will need to implement the marker interface IRequest<TResponse>
(where TResponse
stands for any response type). This also works if your request class implements this interface implicitly, e.g. by implementing IRequest
instead (which is a shortcut for IRequest<Unit>
).
So GetPersonListQuery
implements the request interface which makes it a valid request type for MediatR. In order for a handler to be able to respond to it, that handler will now need to implement IRequestHandler<GetPersonListQuery>
(again, implicitly is also ok).
So you now have this constellation:
public class GetPersonListQuery : IRequest { }
public class PersonListHandler : IRequestHandler<GetPersonListQuery> { }
From the point of view of using MediatR, this is usually all that you will need to do. MediatR will connect these two, so the handler runs when your request is sent.
It does that by relying on dependency injection, most commonly the Microsoft.Extensions.DependencyInjection
container. The dependency injection container basically works by collecting a list of service registrations that contain both a service type and an implementation type. When the container then needs to resolve an instance of the service type, it will look up the service registration for that and construct an object by instantiating the implementation type.
In our example, the PersonListHandler
is the implementation type, and the IRequestHandler<GetPersonListQuery>
interface is the service type. So when something requests an IRequestHandler<GetPersonListQuery>
from the DI container, the DI container will respond to it with an instance of PersonListHandler
.
So MediatR doesn’t actually do that much; in fact, the most of its complexity comes from abstracting the dependency injection container so that it can work with others as well. But if you wanted to implement this with a specific DI container in mind, the implementation could look like this (simplified pseudo-code):
public Task<object> Send(IRequest request)
{
var requestType = request.GetType(); // GetPersonListQuery
var handlerType = CreateHandlerType(requestType); // IRequestHandler<GetPersonListQuery>
var handler = GetService(handlerType); // PersonListHandler object
return await handler.Run(request);
}
So the magic mostly boils down to the magic from the DI container, with MediatR making use of the container’s ability to resolve services.
But how does the DI container know which handlers to register? That is pretty easy to explain: When you do services.AddMediatR(…)
then you are essentially telling MediatR to actively scan the assembly for all types that implement IRequestHandler<T>
and register those with the dependency injection container. So just by adding a new request handler to your project, AddMediatR
will pick that up when the application launches, making it available to the DI container and by that to the MediatR mediator implementation.
Since MediatR relies on the DI container for resolving the request handlers, this also gives you a direct answer to the other question you had: What happens when you have multiple request handlers for one request type?
The DI container by default will only ever give you one instance when resolving a service. This means that when you have multiple handlers implementing IRequestHandler<GetPersonListQuery>
, you will only get one of them. Usually, the DI container will use the latest service registration to resolve a service. So if you register two implementation types for the same service type, the later registration will overwrite the former.
As you don’t register the services yourself with AddMediatR
, you cannot actively control the registration order. Since it’s iterating over all types, I would expect the registration order to be alphabetically, but I wouldn’t rely on that. If you want a specific handler to run, you should just avoid having a different handler in the same assembly.
Note that there are cases where multiple service registrations do work: The whole notification concept in MediatR (INotification
and INotificationHandler
) is made for the ability for multiple handlers to exist. This is using a different feature with the dependency injection container which allows you to resolve instances for all service registrations. So if you have two handlers for the same notification, both handlers will be returned by the DI container and MediatR can call them both. The rest of the handling in MediatR is really the same as with requests though.
Upvotes: 17
Reputation: 822
At runtime, MediatR scans your project for classes implementing IRequestHandler<T>
and makes note of each of them (including your IRequestHandler<GetPersonListQuery>
). When you use _mediator.Send
, it matches the type of the parameter to a matching IRequestHandler
it found during app startup.
Upvotes: 3
Reputation: 119
when you create Query/Command, you use IRequest interface. then in handler you call IRequestHandler<Query/Command>. here they make relation.
Upvotes: 2