There are two parts to this question. First I am going to explain what I think is happening? And then second, in that explanation, I want to verify how it is finding a class it needs.
This is from Nick Chapsas' program Write cleaner APIs in .NET 7 with MediatR.
This code handles a request using MediatR as follows.
n program.cs it has:
app.MediateGet<ExampleRequest>("example/{name}");
which is called via the extension function:
public static WebApplication MediateGet<TRequest>(
this WebApplication app,
string template) where TRequest : IHttpRequest
{
app.MapGet(template, async (IMediator mediator,
[AsParameters] TRequest request) => await mediator.Send(request));
return app;
}
where IHttpRequest is defined as:
public interface IHttpRequest : IRequest<IResult>
{
}
So what has occurred from all this is me have an app.MapGet() call for the url "example/{name}" and it will be handles by MediatR.
ExampleRequest.cs is a POCO containing the URL parameters, etc.
The handler must implement the interface IRequestHandler<ExampleRequest, IResult>. When that url is requested, it will instantiate the appropiate class and call its Handle() method passing in the ExampleRequest and getting a Task returned.
Question 1: Is that correct?
There is the following class in the program that is a handler that implements the require interface. And it is called by MediatR.
public class ExampleHandler : IRequestHandler<ExampleRequest, IResult>
{
private readonly GuidService _guidService;
public ExampleHandler(GuidService guidService)
{
_guidService = guidService;
}
public async Task<IResult> Handle(
ExampleRequest request, CancellationToken cancellationToken)
{
await Task.Delay(10, cancellationToken);
return Results.Ok(new
{
message = $"The age was: {request.Age} and the name was: {request.Name}",
requestGuid = request.GuidService.Id,
ctorGuid = _guidService.Id
});
}
}
Question 2a: How does MediatR find this class? It is not passed to anything or registered with anything. Does MediatR scan all classes looking for a match?
Question 2b: And if so, what if there are two matches?
Question 2c: Also if so, is there a way to register handler classes to speed up finding them and avoid duplicate matches?
1. Effectively, yes. The idea is that you're tying individual endpoint operations to a responsible request/response handler, for which a concrete implementation class may implement one or several; the endpoint is abstracted from that responsibility and just needs to know about it's own required input and output pair, resolution is handled by MediatR.
2a. Reflection and assembly scanning. The registration process asks for assemblies to scan, and the MediatR library will locate and attempt to register classes that fulfill its interfaces
https://github.com/jbogard/MediatR/blob/master/src/MediatR/Registration/ServiceRegistrar.cs
MediatR supports Microsoft.Extensions.DependencyInjection.Abstractions directly. To register various MediatR services and handlers:
services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblyContaining()); or with an assembly:
services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Startup).Assembly));
This registers:
- IMediator as transient
- ISender as transient
- IPublisher as transient
- 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
This also registers open generic implementations for:
- INotificationHandler<>
- IRequestPreProcessor<>
- IRequestPostProcessor<,>
- IRequestExceptionHandler<,,>
- IRequestExceptionAction<,>
2b. Which takes precedence depends on how you register them, but MediatR will only resolve one IRequestHandler for a given type. The assembly scanner will register the first located handler and will skip any additional ones it finds. If you manually register multiple, it will use the last registered.
2c. Yes. You do not have to use the automated assembly scanning via the registration extensions to register MediatR; you can manually register the implementations of IMediator, ISender, and IPublisher yourself, and then selectively register all of your various handlers manually into the IoC container.