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

Gol*_*Age 15 c# mediatr asp.net-core

我在我的asp.net核心项目中使用CQS模式.让我们从一个例子开始,以更好地解释我想要实现的目标.我创建了一个命令:

public class EmptyCommand : INotification{}
Run Code Online (Sandbox Code Playgroud)

命令处理程序:

public class EmptyCommandHandler : INotificationHandler<EmptyCommand>
{
    public Task Handle(EmptyCommand notification, CancellationToken cancellationToken)
    {
        return Task.FromResult(string.Empty);
    }
}
Run Code Online (Sandbox Code Playgroud)

查询:

public class EmptyQuery : IRequest<string>{}
Run Code Online (Sandbox Code Playgroud)

查询处理程序:

public class EmptyQueryHandler : IRequestHandler<EmptyQuery, string>
{
    public Task<string> Handle(EmptyQuery notification, CancellationToken cancellationToken)
    {
        return Task.FromResult(string.Empty);
    }
}
Run Code Online (Sandbox Code Playgroud)

这是一个简单的例子,说明如何从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();  
}
Run Code Online (Sandbox Code Playgroud)

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

我正在考虑创建public class MediatorBoostrapper : IMediator 但不确定该课程的正确实施应该是什么,如果我的想法是好的.有任何想法吗?干杯

编辑

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

  2. 我想有一个如何使用Polymorphic dispatch 创建GenericCommandHandler和GenericQueryHandler的示例

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

Gol*_*Age 6

这次我想从最后开始回答这个问题.

2.

TL; DR Polymorphic Dispatch不能用于CQS

在使用MediatR库一段时间后,阅读我的问题下的评论并与我的朋友协商,我发现只有在命令的情况下,Polymorphic Dispatch(PD)才能用于创建通用处理程序.无法为查询实施PD解决方案.根据文档,处理程序是逆变的而不是协变的.这意味着PD 仅适用TResponse常量类型的情况.如果是查询,则为false,每个Query处理程序都可以返回不同的结果.

我也发现了这个问题.我认为有趣的是,只有当容器支持时才能使用Polymorphic Dispatch.

1. 使用MediatR时,行为是CQS的唯一解决方案.根据我在#Steve的问题中的评论和jbogard的评论,我找到了如何使用Behaviors和IRequestHandler来实现严格的Command模式.完整评论:

只是总结一下这些变化,有两种主要的请求:返回值的那些,以及那些不返回值的请求.那些现在没有实现的IRequest<T>地方T : 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){...}
}
Run Code Online (Sandbox Code Playgroud)

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");
    }
}
Run Code Online (Sandbox Code Playgroud)

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;
    }

}
Run Code Online (Sandbox Code Playgroud)

d)注册LoggingBehavior添加命令

services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
Run Code Online (Sandbox Code Playgroud)

ConfigureServicesStartup.cs中方法的主体.

e)如何运行示例命令和查询的示例:

await _mediator.Send(new EmptyCommand());
var result = await _mediator.Send(new EmptyQuery());
Run Code Online (Sandbox Code Playgroud)


Hen*_*ema 5

MediatR支持向通用处理程序调度通知(多态调度)。例如:

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

对于通过发出的每个通知,都会调用此处理程序Publish()。对于请求(查询/命令)也是如此。您还应该看看行为

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