在 asp .net 核心中为 MediatR 库的发送和发布方法添加通用处理程序

Add a generic handler for Send and Publish methods of the MediatR library in asp .net core

我在我的 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();  
}

请记住,查询可以 return 其他类型不一定是 string。 我想创建某种桥梁 class 例如MediatorBoostrapper,这允许我在每次调用 Publish 方法时 运行 一些业务逻辑(例如通过 Logger 记录 command/query),然后 从命令处理程序调用 public Task Handle(EmptyCommand notification,... 方法。该解决方案必须是通用的,因此每次我 运行 Publish 方法时都会调用此方法。我也希望能够为 Send 方法做同样的事情。

我正在考虑创建 public class MediatorBoostrapper : IMediator 但不确定 class 应该如何正确实施以及我的想法是否正确。 有任何想法吗?干杯

编辑

  1. 我想举例说明如何使用 Behaviors 每次我 运行 Send 方法进行查询时,都可以创建一种从通用处理程序 运行 某些外部方法的通用方法。我想为 Publish 方法提供一个类似的示例,我用它来发送命令。

  2. 我想要一个如何使用的例子Polymorphic dispatch 用于创建 GenericCommandHandler 和 GenericQueryHandler

我在 GitHub 上创建了一个示例项目,可以找到 here 您可以随意尝试使用您的解决方案扩展此项目。

MediatR 支持将通知分派给通用处理程序 (polymorphic dispatch)。例如:

public class GenericHandler<TNotification> : INotificationHandler<TNotification> 
    where TNotification : INotification
{
    public Task Handle(TNotification notification, CancellationToken cancellationToken)
    {
        return Task.CompletedTask;
    }
}

对于通过 Publish() 发布的每个通知,都会调用此处理程序。请求也是如此(queries/commands)。您还应该看看 behaviors.

如果您将 MediatR 与 ASP.NET 核心一起使用,我建议您使用 MediatR.Extensions.Microsoft.DependencyInjection 库,该库负责将所有处理程序连接在一起。

这次我想从头开始回答问题。

2.

TL;DR Polymorphic Dispatch cannot be used for the CQS

在玩了一段时间 MediatR 库后,阅读了我的问题下的评论并咨询了我的朋友,我发现 Polymorphic Dispatch(PD) 可用于创建仅在命令情况下的通用处理程序。 Queries 无法实现 PD 方案。基于 Documentation,处理程序是 逆变 而不是协变的。这意味着在 TResponse 是常量类型的情况下,PD 仅 有效。在查询的情况下,这是错误的,每个查询处理程序可以 return 不同的结果。

我也找到了this issue。我认为了解只有在您的容器支持的情况下才能使用多态调度很有趣。

1. Behaviors 是使用 MediatR 时唯一的 CQS 解决方案。 根据#Steve 和 comment from jbogard 在我的问题下发表的评论,我找到了如何使用 Behaviors 和 IRequestHandler 来实现严格的命令模式。完整评论:

Just to summarize the changes, there are 2 main flavors of requests: those that return a value, and those that do not. The ones that do not now implement IRequest<T> where T : Unit. This was to unify requests and handlers into one single type. The diverging types broke the pipeline for many containers, the unification means you can use pipelines for any kind of request.

It forced me to add the Unit type in all cases, so I've added some helper classes for you.

  • IRequestHandler<T> - implement this and you will return Task<Unit>.
  • AsyncRequestHandler<T> - inherit this and you will return Task.
  • RequestHandler<T> - inherit this and you will return nothing (void).

For requests that do return values:

  • IRequestHandler<T, U> - you will return Task<U>
  • RequestHandler<T, U> - you will return U

I got rid of the AsyncRequestHandler because it really wasn't doing anything after the consolidation, a redundant base class.

例子

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 class:

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());