在 asp .net 核心中为 MediatR 库的发送和发布方法添加通用处理程序
Posted
技术标签:
【中文标题】在 asp .net 核心中为 MediatR 库的发送和发布方法添加通用处理程序【英文标题】:Add a generic handler for Send and Publish methods of the MediatR library in asp .net core 【发布时间】:2019-05-22 13:26:10 【问题描述】:我在我的 asp.net 核心项目中使用 CQS 模式。让我们从一个例子开始,以更好地解释我想要实现的目标。我创建了一个命令:
public class EmptyCommand : INotification
命令处理程序:
public class EmptyCommandHandler : INotificationHandler<EmptyCommand>
public Task Handle(EmptyCommand notification, CancellationToken cancellationToken)
return Task.FromResult(string.Empty);
查询:
public class EmptyQuery : IRequest<string>
查询处理程序:
public class EmptyQueryHandler : IRequestHandler<EmptyQuery, string>
public Task<string> Handle(EmptyQuery notification, CancellationToken cancellationToken)
return Task.FromResult(string.Empty);
这是一个简单的示例,说明如何从 EmptyCommandHandler 和 EmptyQueryHandler 运行命令和查询并调用 Handle 方法:
readonly IMediator _mediator;
public HomeController(IMediator mediator)
_mediator = mediator;
public async Task<IActionResult> Index()
await _mediator.Publish(new EmptyCommand());
var queryResult = await _mediator.Send(new EmptyQuery());
return View();
请记住,查询可以返回其他类型,不一定是string
。
我想创建某种桥梁课程,例如MediatorBoostrapper
,它允许我在每次调用 Publish 方法时运行一些业务逻辑(例如,通过 Logger 记录命令/查询),然后
从命令处理程序调用public Task Handle(EmptyCommand notification,...
方法。解决方案必须是通用的,因此每次运行Publish
方法时都会调用此方法。我也希望能够为 Send
方法做同样的事情。
我正在考虑创建public class MediatorBoostrapper : IMediator
但不确定该类的正确实现应该是什么,以及我的想法是否好。
有任何想法吗?干杯
编辑
我想举个例子说明如何使用Behaviors
每次我为查询运行Send
方法时,创建一种通用方法来从通用处理程序运行一些外部方法。我想为Publish
方法提供一个类似的示例,用于发送命令。
我想举个例子说明如何使用Polymorphic dispatch 用于创建 GenericCommandHandler 和 GenericQueryHandler
我在 GitHub 上创建了一个示例项目,可以找到 here 您可以随意尝试使用您的解决方案扩展此项目。
【问题讨论】:
你能解释一下你在这门课上要做什么吗?记住 CQS 中的 S 代表什么:分离。不要在每个应该是单独逻辑的调用中包含逻辑。如果它足够简单,只需将其添加到您的处理程序中。或者扩展类,但这在很大程度上取决于它需要做什么。 github.com/jbogard/MediatR/wiki/Behaviors 我想创建某种中间件,这样我就可以使用记录器记录每个命令和查询 在这种情况下,我要遵守 DRY 规则。 彼得这很有趣,需要尝试这种方法,看看我能实现什么 【参考方案1】:这次我想从头开始回答这个问题。
2.
TL;DR 多态调度不能用于 CQS
在玩了一段时间的 MediatR 库后,阅读了我的 Question 下的 cmets 并咨询了我的朋友,我发现 Polymorphic Dispatch(PD) 只能在命令的情况。 PD 解决方案无法针对 Queries 实施。基于Documentation,处理程序是逆变而不是协变的。这意味着 PD仅在 TResponse
是常量类型的情况下工作。在查询的情况下,这是错误的,每个查询处理程序都可以返回不同的结果。
我还找到了this issue。我认为只有在容器支持的情况下才能使用 Polymorphic Dispatch,这很有趣。
1. Behaviors 是使用 MediatR 时 CQS 的唯一且唯一的解决方案。 根据我在#Steve 和comment from jbogard 的问题下的评论,我找到了如何将 Behaviors 和 IRequestHandler 用于严格的命令模式的方法。完整评论:
总结一下变化,请求主要有两种风格: 那些返回值的,以及那些不返回值的。那些不 现在实现
IRequest<T>
whereT : Unit
。这是为了统一请求 和处理程序合并为一种类型。不同的类型打破了 许多容器的管道,统一意味着您可以使用 任何类型的请求的管道。它迫使我在所有情况下都添加 Unit 类型,所以我为你添加了一些帮助类。
IRequestHandler<T>
- 执行此操作,您将返回Task<Unit>
。AsyncRequestHandler<T>
- 继承它,你将返回Task
。RequestHandler<T>
- 继承它,你将不会返回任何东西(void)
。对于确实返回值的请求:
IRequestHandler<T, U>
- 你将返回Task<U>
RequestHandler<T, U>
- 你将返回U
我去掉了 AsyncRequestHandler,因为它在合并后真的没有做任何事情,一个冗余的基类。
示例
a) 命令管理:
public class EmptyCommand : IRequest...
public class EmptyCommandHandler : RequestHandler<EmptyCommand>
protected override void Handle(EmptyCommand request)...
b) 查询管理:
// can be any other type not necessarily `string`
public class EmptyQuery : IRequest<string>...
public class EmptyQueryHandler : IRequestHandler<EmptyQuery, string>
public Task<string> Handle(EmptyQuery notification, CancellationToken cancellationToken)
return Task.FromResult("Sample response");
c) 示例LogginBehavior
类:
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;
public LoggingBehavior(ILogger<LoggingBehavior<TRequest, TResponse>> logger)
_logger = logger;
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
var requestType = typeof(TRequest).Name;
var response = await next();
if (requestType.EndsWith("Command"))
_logger.LogInformation($"Command Request: request");
else if (requestType.EndsWith("Query"))
_logger.LogInformation($"Query Request: request");
_logger.LogInformation($"Query Response: response");
else
throw new Exception("The request is not the Command or Query type");
return response;
d) 要注册LoggingBehavior
,请添加命令
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
到 Startup.cs 中 ConfigureServices
方法的主体。
e) 如何运行示例命令和查询示例:
await _mediator.Send(new EmptyCommand());
var result = await _mediator.Send(new EmptyQuery());
【讨论】:
【参考方案2】:MediatR 支持将通知发送到通用处理程序 (polymorphic dispatch)。例如:
public class GenericHandler<TNotification> : INotificationHandler<TNotification>
where TNotification : INotification
public Task Handle(TNotification notification, CancellationToken cancellationToken)
return Task.CompletedTask;
将为通过Publish()
发布的每个通知调用此处理程序。请求(查询/命令)也是如此。你也应该看看behaviors。
如果您将 MediatR 与 ASP.NET Core 一起使用,我建议您使用 MediatR.Extensions.Microsoft.DependencyInjection 库,它负责将所有处理程序连接在一起。
【讨论】:
在我将GenericHandler
添加到我的项目并尝试运行解决方案后,我收到了错误:Cannot instantiate implementation type 'HighElo.Domain.CommandHandlers.GenericHandler
我是否需要向 Startup.cs 添加一些额外的配置?
@GoldenAge 看看这个库:github.com/jbogard/…。它处理请求处理程序的注册。
我已经在使用这个库了。我尝试使用许多不同的方法注册GenericHandler
,例如services.AddMediatR(typeof(GenericHandler<EmptyCommand>));
仍然会抛出同样的错误。不知道我应该如何注册它..
您只需要调用services.AddMediatR()
,它就会将所有请求及其处理程序连接在一起。
所以,我创建了一个完全独立的解决方案来测试你的想法,我将它上传到 GitHub 并且它可以工作。当我将相同的代码添加到我现在正在处理的项目中时,我收到了我之前提到的这个奇怪的错误。您可以在此处找到示例:github.com/demos666/mediatr-generic-handler 在我的例子中,我将services.AddMediatR(typeof(Startup),typeof(EmptyClass));
添加到 Startup.cs 以使其工作,因为我将命令和查询存储在单独的项目中。我还需要实现GenericQueryHandler
。以上是关于在 asp .net 核心中为 MediatR 库的发送和发布方法添加通用处理程序的主要内容,如果未能解决你的问题,请参考以下文章
Mediatr 无法解析 ASP.Net Core 中的 UserManager
ASP.NET Core MediatR 错误:向容器注册处理程序
ASP.NET Core MVC 入门到精通 - 3. 使用MediatR
如何为 ASP.NET Core 注册和使用 MediatR 管道处理程序?