在C# .NET 8中使用MediatR实例详细介绍

作者:微信公众号:【架构师老卢】
5-10 10:47
177

概述:MediatR 作为 .NET 的 NuGet 包提供,它体现了调解器设计模式,这是一种旨在解耦对象之间通信的策略。MediatR 是 .NET 中此模式的一个成熟实现,其官方 GitHub 项目可在此处找到:MediatR 基础知识从本质上讲,MediatR 在三种主要模式下运行:Request:涉及具有服务响应的单个接收方。Notification:在没有服务响应的情况下与多个接收方接合。StreamRequest:利用单个接收器进行具有服务响应的流操作。就本文而言,我们主要关注 Request 行为,尤其是探索 MediatR 管道。MediatR Pipelines在中介请求流中,发布

MediatR 作为 .NET 的 NuGet 包提供,它体现了调解器设计模式,这是一种旨在解耦对象之间通信的策略。

MediatR 是 .NET 中此模式的一个成熟实现,其官方 GitHub 项目可在此处找到:

MediatR 基础知识

从本质上讲,MediatR 在三种主要模式下运行:

  • Request:涉及具有服务响应的单个接收方。
  • Notification:在没有服务响应的情况下与多个接收方接合。
  • StreamRequest:利用单个接收器进行具有服务响应的流操作。

就本文而言,我们主要关注 Request 行为,尤其是探索 MediatR 管道。

MediatR Pipelines

在中介请求流中,发布者(发送_操作_)和订阅者(处理程序)之间存在明显的区别。

通过利用 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 Pipeline 使用案例

mediatR 管道的用例很多,它是:

  • 日志记录:使用流水线记录有关请求、响应或执行时间的相关信息。
  • 缓存:在请求的同时实现缓存管道可以最大程度地减少数据库,以防使用 MediatR 请求检索信息。
  • 请求验证:在请求执行之前集成用于验证输入数据的逻辑可确保仅处理有效数据。
  • 工作单元模式:利用管道管理工作单元,便于集中处理事务和回滚操作。

示例实现

启动应用程序,将出现 Swagger 页面:

您可以找到 4 个不同的终结点:

  • SampleCommand:使用此终结点测试命令。此命令通过所有 MediatR 管道:日志记录 -> 验证 -> 授权。
  • SampleRequest:使用此终结点跳过所有管道。这之所以有效,是因为它使用普通而不是自定义。IRequestICommand
  • SampleEntity:此终结点可用于测试添加 Sample 实体终结点的结果。
  • AddSampleEntity:此终结点使用接口,是工作单元管道行为的示例。在此项目中,它还实现了 IRepository 模式的示例,该模式使用 EF 和 InMemory 数据库。ITransactionCommand

在本文中,我们将仅关注前 2 个终结点:SampleCommandSampleRequest

API Flow — Code Explaination

下面是命令和请求终结点流的详细说明。

命令

在端点后面,我们有一个中介器接口,它发送一个 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 };
    }
}

Pipeline — Code Explaination

现在,让我们深入研究 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

相关代码下载地址
重要提示!:取消关注公众号后将无法再启用回复功能,不支持解封!
第一步:微信扫码关键公众号“架构师老卢”
第二步:在公众号聊天框发送code:72413,如:code:72413 获取下载地址
第三步:恭喜你,快去下载你想要的资源吧
阅读排行