MediatR 作为 .NET 的 NuGet 包提供,它体现了调解器设计模式,这是一种旨在解耦对象之间通信的策略。
MediatR 是 .NET 中此模式的一个成熟实现,其官方 GitHub 项目可在此处找到:
从本质上讲,MediatR 在三种主要模式下运行:
就本文而言,我们主要关注 Request 行为,尤其是探索 MediatR 管道。
在中介请求流中,发布者(发送_操作_)和订阅者(处理程序)之间存在明显的区别。
通过利用 MediatR 管道,我们可以有效地拦截此流程,并将自定义逻辑引入流程。
要实现管道,需要从接口继承,如下所示:IPipelineBehavior<TRequest, TResponse>
public sealed class MyPipelineBehavior<TRequest, TResponse>
: IPipelineBehavior<TRequest, TResponse>
{
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
// Pre-processing logic
var response = await next();
// Post-processing logic
return response;
}
}
如提供的代码片段所示,此方法允许在调用调解器管道中的后续步骤之前和之后插入逻辑。
此外,创建多个按顺序注册的管道行为有助于建立有凝聚力的行为链。
如果需要控制中介器管道的执行顺序,只需按照希望调用行为的顺序注册行为即可。
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(MyPipelineBehavior1<,>));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(MyPipelineBehavior2<,>));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(MyPipelineBehavior3<,>));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(MyPipelineBehavior4<,>));
本项目中采用的另一项值得注意的技术涉及自定义 MediatR 的默认界面。IRequest
通过继承默认接口并构建自定义接口(例如 ),我们可以显式筛选特定接口的管道。IRequestICommand
自定义的示例实现:IRequest
public interface ICommand<out TResponse> : IRequest<TResponse>
{
}
专为以下目的量身定制的管道实例化示例:ICommand
public sealed class MyPipelineBehavior<TRequest, TResponse>
: IPipelineBehavior<TRequest, TResponse>
where TRequest : ICommand<TResponse>
mediatR 管道的用例很多,它是:
启动应用程序,将出现 Swagger 页面:
您可以找到 4 个不同的终结点:
在本文中,我们将仅关注前 2 个终结点:SampleCommand 和 SampleRequest。
下面是命令和请求终结点流的详细说明。
在端点后面,我们有一个中介器接口,它发送一个 Sample 命令。SampleCommand
app.MapPost("/SampleCommand", ([FromBody] SampleBody sampleBody, IMediator mediator) =>
{
return mediator.Send(new SampleCommand() { Id = Guid.NewGuid(), Description = sampleBody.Description, EventTime = DateTime.UtcNow });
})
.WithName("SampleCommand")
.WithOpenApi();
下面是该类的定义及其返回类:SampleCommand
public class SampleCommand : ICommand<SampleCommandComplete>
{
public Guid Id { get; set; }
public DateTime EventTime { get; set; }
public string Description { get; set; }
}
public record SampleCommandComplete
{
public Guid Id { get; set; }
}
如前所述,该接口继承了 MediatR 的标准接口。ICommandIRequest
public interface ICommand<out TResponse> : IRequest<TResponse>
{
}
下面是处理程序实现;在这种情况下,它只是记录一些信息并创建响应:
public class SampleCommandHandler : IRequestHandler<SampleCommand, SampleCommandComplete>
{
private readonly ILogger<SampleCommandHandler> _logger;
public SampleCommandHandler(ILogger<SampleCommandHandler> logger)
{
_logger = logger;
}
public async Task<SampleCommandComplete> Handle(SampleCommand request, CancellationToken cancellationToken)
{
_logger.LogInformation("Command Executed Id:{Id};Description:{Description};EventTime:{EventTime}", request.Id, request.Description, request.EventTime);
return new SampleCommandComplete() { Id = request.Id };
}
}
请求流或多或少与命令流相同。在 API 后面,它只是创建一个并使用 IMediator 接口发送它。SampleRequest
app.MapPost("/SampleRequest", ([FromBody] SampleBody sampleBody, IMediator mediator) =>
{
return mediator.Send(new SampleRequest() { Id = Guid.NewGuid(), Description = sampleBody.Description, EventTime = DateTime.UtcNow });
})
.WithName("SampleRequest")
.WithOpenApi();
在这种情况下,just 直接继承自标准 .SampleRequestIRequest
public class SampleRequest : IRequest<SampleRequestComplete>
{
public Guid Id { get; set; }
public DateTime EventTime { get; set; }
public string Description { get; set; }
}
public record SampleRequestComplete
{
public Guid Id { get; set; }
}
下面是处理程序实现;在这种情况下,它只是记录一些信息并创建响应:
public class SampleRequestHandler : IRequestHandler<SampleRequest, SampleRequestComplete>
{
private readonly ILogger<SampleRequestHandler> _logger;
public SampleRequestHandler(ILogger<SampleRequestHandler> logger)
{
_logger = logger;
}
public async Task<SampleRequestComplete> Handle(SampleRequest request, CancellationToken cancellationToken)
{
_logger.LogInformation("Request Executed Id:{Id};Description:{Description};EventTime:{EventTime}", request.Id, request.Description, request.EventTime);
return new SampleRequestComplete() { Id = request.Id };
}
}
现在,让我们深入研究 MediatR Pipelines 的实际用例。
在此项目中,所有管道都引用接口。因此,当您尝试前面介绍的两个端点时,您可以看到只有 对管道流感兴趣。ICommandSampleCommand
第一个用例是在执行请求之前和之后记录信息。在此用例中,管道记录请求的执行时间。
public sealed class LoggingBehavior<TRequest, TResponse>
: IPipelineBehavior<TRequest, TResponse>
where TRequest : ICommand<TResponse>
{
private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;
public LoggingBehavior(ILogger<LoggingBehavior<TRequest, TResponse>> logger)
{
_logger = logger;
}
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
Stopwatch stopwatch = new();
_logger.LogInformation($"Handling {typeof(TRequest).Name}");
stopwatch.Start();
var response = await next();
stopwatch.Stop();
_logger.LogInformation($"Handled {typeof(TResponse).Name} in {stopwatch.ElapsedMilliseconds} ms");
stopwatch.Reset();
return response;
}
}
另一个用例是在执行请求之前验证输入消息请求。
public sealed class ValidationBehavior<TRequest, TResponse>
: IPipelineBehavior<TRequest, TResponse>
where TRequest : ICommand<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
var context = new ValidationContext<TRequest>(request);
var validationFailures = await Task.WhenAll(
_validators.Select(validator => validator.ValidateAsync(context)));
var errors = _validators
.Select(x => x.Validate(context))
.SelectMany(x => x.Errors)
.Where(x => x != null);
if (errors.Any())
{
throw new ValidationException(errors);
}
var response = await next();
return response;
}
}
此验证管道是通用的;为了为每条消息自定义它,我使用了一个名为 FluentValidation 的库。
因此,若要定义验证,只需创建一个继承自以下代码的类,如以下代码所示AbstractValidator
public sealed class SampleCommandValidator : AbstractValidator<SampleCommand>
{
public SampleCommandValidator()
{
RuleFor(command => command.Id)
.NotEmpty()
.NotEqual(Guid.Empty);
RuleFor(command => command.Description)
.NotEmpty();
}
}
与验证管道类似,我们可以实现自定义授权管道。
为此,我们注入了一个自定义并在预处理部分使用它。IAuthService
public class CommandAuthorizationBehavior<TRequest, TResponse>
: IPipelineBehavior<TRequest, TResponse>
where TRequest : ICommand<TResponse>
{
private readonly IAuthService _authService;
public CommandAuthorizationBehavior(IAuthService authService)
{
_authService = authService;
}
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
var response = _authService.OperationAllowed();
if (!response.IsSuccess)
throw response.Exception ?? new Exception();
return await next();
}
}
如果尝试该服务,它可能会返回错误,因为该方法的返回是随机的。这种随机性是使用 Bogus 完成的,Bogus 是一个有用的 .NET NuGet 包,用于创建虚假数据。
在域项目中,您将找到一个注册所有依赖项的扩展方法。
public static IServiceCollection AddMediatorSample(this IServiceCollection services)
{
services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(ServicesExtensions).Assembly));
//Just register the behaviors in the order you would like them to be called.
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(CommandAuthorizationBehavior<,>));
services.AddValidatorsFromAssembly(typeof(ServicesExtensions).Assembly);
services.AddTransient<IAuthService, AuthService>();
return services;
}
源代码获取:公众号回复消息【code:72413
】