MediatR在其机制中大量使用泛型类型。从使用它时您可能已经注意到,为了使您的请求和处理程序可用于MediatR,您需要以正确的方式使用某些接口。
让我们以您的示例来解释这一切是如何工作的:
await _mediator.Send(new GetPersonListQuery());
为了使
GetPersonListQuery
被接受,它需要实现标记接口
IRequest<TResponse>
(其中
TResponse
代表任何响应类型)。如果您的请求类隐式地实现了此接口,例如通过实现
IRequest
(这是
IRequest<Unit>
的快捷方式),则也可以运行。因此,
GetPersonListQuery
实现了请求接口,使其成为 MediatR 的有效请求类型。为了使处理程序能够对其进行响应,该处理程序现在将需要实现
IRequestHandler<GetPersonListQuery>
(同样,隐式实现也可以)。因此,您现在有了这个星座:
public class GetPersonListQuery : IRequest { }
public class PersonListHandler : IRequestHandler<GetPersonListQuery> { }
从使用 MediatR 的角度来看,这通常是你需要做的全部。MediatR 将连接它们,因此当发送请求时处理程序将运行。
它依赖于依赖注入,最常见的是 Microsoft.Extensions.DependencyInjection 容器。依赖注入容器基本上通过收集包含服务类型和实现类型的服务注册列表来工作。然后,当容器需要解析服务类型的实例时,它将查找该服务的服务注册表,并通过实例化实现类型来构造一个对象。
在我们的示例中,PersonListHandler 是实现类型,而 IRequestHandler<GetPersonListQuery> 接口是服务类型。因此,当某些东西从 DI 容器请求 IRequestHandler<GetPersonListQuery> 时,DI 容器将用 PersonListHandler 实例响应它。
所以 MediatR 实际上并没有做太多事情;事实上,它大部分的复杂性来自于抽象化依赖注入容器,使其也可以与其他容器一起工作。但是如果您想使用特定的 DI 容器来实现这个功能,那么实现看起来可能像这样(简化伪代码):
public Task<object> Send(IRequest request)
{
var requestType = request.GetType();
var handlerType = CreateHandlerType(requestType);
var handler = GetService(handlerType);
return await handler.Run(request);
}
因此,魔法大部分都归结于 DI 容器的魔法,MediatR 利用容器解析服务的能力。但是,DI 容器如何知道要注册哪些处理程序呢?这很容易解释:当您执行 services.AddMediatR(...)时,实际上是告诉 MediatR 主动扫描程序集,查找所有实现 IRequestHandler 的类型,并将它们注册到依赖注入容器中。因此,只要在项目中添加新的请求处理程序,AddMediatR 在应用程序启动时就会捕获到它,使其可用于 DI 容器,并由此可以让 MediatR 中介实现使用。
由于 MediatR 依赖于 DI 容器以解析请求处理程序,这也直接回答了您提出的另一个问题:当一个请求类型有多个请求处理程序时会发生什么?
默认情况下,DI 容器只会在解析服务时提供一个实例。这意味着,当您有多个实现 IRequestHandler 的处理程序时,您只会得到其中一个。通常,DI 容器将使用最新的服务注册来解析服务。因此,如果您为同一服务类型注册了两个实现类型,则后者将覆盖前者。
由于您不能使用 AddMediatR 自己注册服务,因此无法主动控制注册顺序。由于它会遍历所有类型,因此我期望注册顺序是按字母顺序排列的,但我不会依赖于这一点。如果您想要运行特定的处理程序,则应避免在同一个程序集中拥有不同的处理程序。
请注意,有些情况下,多个服务注册确实有效:MediatR 中的整个通知概念(INotification 和 INotificationHandler)就是为了支持多个处理程序而设计的。这使用依赖注入容器中的不同功能,该功能允许您解析出所有服务注册的实例。因此,如果您有两个针对同一通知的处理程序,则 DI 容器将返回这两个处理程序,MediatR 可以同时调用它们。MediatR 中的其余处理与请求完全相同。