C# .NET 8 — 使用 MediatR 流式传输请求和管道

789095411c7d7a21dd85fc8c3ba0ebef.jpeg

MediatR 是一个库,它自称为“.NET 中的简单调解器实现”。(本文源代码文末提供下载)

在本文中,我们将探讨 MediatR 中请求的异常处理过程,特别关注 MediatR 实体只有一个接收方的场景。

有关MediatR的进一步见解,请随时浏览我就此主题撰写的其他文章。

流请求

普通请求可用于命令和查询操作。流请求专门设计用于使用 MediatR 检索一组流数据。

为了使用 Mediator 处理流请求,需要使用特定的接口 。IStreamRequest

与标准请求非常相似,它使用单个接收器运行,并且来自 MediatR 的返回对象是一个集合。IAsyncEnumerable

我不会在这里深入研究如何在 C# 中处理流;我将来可能会单独写一篇关于它的文章。

为了简短起见,提供流需要使用语句。AsyncEnumerable()yield

在 C# 中,关键字有助于对集合进行迭代,一次返回一个元素。此方法允许在接收数据时返回数据,使调用方能够立即处理数据。yield

示例实现

这是我准备的一个项目的链接:文末提供下载。

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

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

  • SampleCommand:使用此终结点测试命令。此命令通过所有 MediatR 管道:日志记录 -> 验证 -> 授权。

  • SampleRequest:使用此终结点跳过所有管道。这之所以有效,是因为它使用普通的 IRequest 而不是自定义的 ICommand。

  • SampleEntity:此终结点可用于测试添加 Sample 实体终结点的结果。

  • AddSampleEntity:此终结点使用 ITransactionCommand 接口,是工作单元管道行为的示例。在此项目中,它还实现了 IRepository 模式的示例,该模式使用 EF 和 InMemory 数据库。

  • SampleStreamEntity:利用纯 IStreamRequest,此终结点从数据库中检索数据。

  • SampleStreamEntityWithPipeFilter:与上一个终结点类似,此终结点也使用普通 IStreamRequest 从数据库中提取数据。但是,它包含应用于请求的管道行为。

出于本文的目的,我们将深入研究最后两个终结点的复杂性:SampleStreamEntity 和 SampleStreamEntityWithPipeFilter

要探索其他端点的功能,请参阅我的另一篇专注于 MediatR 的文章。

代码解释

在本文的以下部分中,我们将了解如何制作 2 个终结点 SampleStreamEntity 和 SampleStreamEntityWithPipeFilter。

检索流

首先,让我们看看如何在 InMemory 配置的上下文中使用 EF Core 检索流。

public async IAsyncEnumerable<T> GetStream(CancellationToken cancellationToken = default)  
    {  
        var datas = _dbSet.AsAsyncEnumerable<T>();  
        await foreach (var data in datas)  
        {  
            if (cancellationToken.IsCancellationRequested)  
                yield break;  
  
            yield return data;  
        }  
    }

示例流实体

现在,让我们深入研究 SampleStreamEntity 终结点的实现,该终结点利用 MediatR 处理请求。

app.MapGet(  
        "/SampleStreamEntity",  
        async (IMediator mediator, CancellationToken cancellationToken) =>  
        {  
            return mediator.CreateStream(new SampleStreamEntityQuery(), cancellationToken);  
        }  
    )  
    .WithName("SampleStreamEntity")  
    .Produces<IAsyncEnumerable<SampleStreamEntityQueryResult>>()  
    .WithOpenApi();

该方法向调用方返回 的集合。CreateStreamIAsyncEnumerable<SampleStreamEntityQueryResult>

该接口是 MediatR 提供的标准接口,专门用于处理流式处理操作。它返回一个名为 的对象。IStreamRequestSampleStreamEntityQueryResult

public class SampleStreamEntityQuery : IStreamRequest<SampleStreamEntityQueryResult> { }  
  
public record SampleStreamEntityQueryResult  
{  
    public Guid Id { get; set; }  
  
    public DateTime EventTime { get; set; }  
  
    public string Description { get; set; }  
}

QueryHandler 实现利用存储库以及从数据库获取流数据的方法。GetStream

public class SampleStreamQueryHandler  
    : IStreamRequestHandler<SampleStreamEntityQuery, SampleStreamEntityQueryResult>  
{  
    private readonly IRepository<SampleEntity> _repository;  
  
    public SampleStreamQueryHandler(IRepository<SampleEntity> repository)  
    {  
        _repository = repository;  
    }  
  
    public async IAsyncEnumerable<SampleStreamEntityQueryResult> Handle(  
 SampleStreamEntityQuery request,  
 CancellationToken cancellationToken  
 )  
    {  
        await foreach (var entity in _repository.GetStream(cancellationToken))  
        {  
            if (cancellationToken.IsCancellationRequested)  
                break;  
  
            yield return new SampleStreamEntityQueryResult()  
            {  
                Id = entity.Id,  
                Description = entity.Description,  
                EventTime = entity.RegistrationTime  
            };  
        }  
    }  
}

使用管道过滤器的示例流图元

第二个终结点 SampleStreamEntityWithPipeFilter 与前一个终结点类似,但包括用于筛选的管道行为。

以下代码片段表示终结点定义:

app.MapGet(  
           "/SampleStreamEntityWithPipeFilter",  
           async (IMediator mediator, CancellationToken cancellationToken) =>  
           {  
               return mediator.CreateStream(  
                   new SampleStreamEntityWithPipeFilterQuery(),  
                   cancellationToken  
               );  
           }  
       )  
       .WithName("SampleStreamEntityWithPipeFilter")  
       .Produces<IAsyncEnumerable<SampleStreamEntityWithPipeFilterQueryResult>>()  
       .WithOpenApi();

下面是 SampleStreamEntityWithPipeFilterQuery 的定义。

public class SampleStreamEntityWithPipeFilterQuery  
    : IStreamRequest<SampleStreamEntityWithPipeFilterQueryResult> { }  
  
public record SampleStreamEntityWithPipeFilterQueryResult  
{  
    public Guid Id { get; set; }  
  
    public DateTime EventTime { get; set; }  
  
    public string Description { get; set; }  
}

这是它的实现:

public class SampleStreamQueryWithPipeFilterHandler  
    : IStreamRequestHandler<  
        SampleStreamEntityWithPipeFilterQuery,  
        SampleStreamEntityWithPipeFilterQueryResult  
    >  
{  
    private readonly IRepository<SampleEntity> _repository;  
  
    public SampleStreamQueryWithPipeFilterHandler(IRepository<SampleEntity> repository)  
    {  
        _repository = repository;  
    }  
  
    public async IAsyncEnumerable<SampleStreamEntityWithPipeFilterQueryResult> Handle(  
 SampleStreamEntityWithPipeFilterQuery request,  
 CancellationToken cancellationToken  
 )  
    {  
        await foreach (var entity in _repository.GetStream(cancellationToken))  
        {  
            if (cancellationToken.IsCancellationRequested)  
                break;  
  
            yield return new SampleStreamEntityWithPipeFilterQueryResult()  
            {  
                Id = entity.Id,  
                Description = entity.Description,  
                EventTime = entity.RegistrationTime  
            };  
        }  
    }  
}

流管道示例

与我之前的文章一样,我的目标是为流请求设计管道行为。

为了实现这一点,我将利用该接口。IStreamPipelineBehavior

此接口允许创建可以截获和修改流式处理请求的自定义行为,从而为应用程序的数据处理管道提供灵活性和可扩展性。

通用流管道示例

将为每个 .这可确保定义的行为一致地应用于所有流式处理请求,从而保持一致性并促进对数据处理管道的集中控制。IStreamRequest

public class GenericStreamLoggingBehavior<TRequest, TResponse>  
    : IStreamPipelineBehavior<TRequest, TResponse>  
{  
    private readonly ILogger<GenericStreamLoggingBehavior<TRequest, TResponse>> _logger;  
  
    public GenericStreamLoggingBehavior(  
 ILogger<GenericStreamLoggingBehavior<TRequest, TResponse>> logger  
 )  
    {  
        _logger = logger;  
    }  
  
    public async IAsyncEnumerable<TResponse> Handle(  
 TRequest request,  
 StreamHandlerDelegate<TResponse> next,  
 [EnumeratorCancellation] CancellationToken cancellationToken  
 )  
    {  
        _logger.LogInformation("Stream Request Start");  
        await foreach (  
            var response in next().WithCancellation(cancellationToken).ConfigureAwait(false)  
        )  
        {  
            _logger.LogInformation(  
                "Processing message {json}",  
                System.Text.Json.JsonSerializer.Serialize(response)  
            );  
  
            yield return response;  
        }  
        _logger.LogInformation("Stream Request End");  
    }  
}

特定流管道的示例

在上一篇文章中,我们创建了一个继承自默认原始请求接口的自定义接口。这使我们能够创建针对特定请求系列量身定制的管道。

但是,在本文中,我将采用不同的方法。在这里,我专门为单个 MediatR 流创建了一个管道。

为了实现这一点,我使用 where 子句直接在实现上指定类类型。IStreamPipelineBehavior

此方法提供了一种更集中、更简化的方法来实现管道,以应用程序体系结构中的特定流为目标。

public class SampleFilterStreamBehavior<TRequest, TResponse>  
    : IStreamPipelineBehavior<TRequest, TResponse>  
    where TRequest : SampleStreamEntityWithPipeFilterQuery  
    where TResponse : SampleStreamEntityWithPipeFilterQueryResult  
{  
    private readonly ILogger<SampleFilterStreamBehavior<TRequest, TResponse>> _logger;  
    private readonly IAuthService _authService;  
  
    public SampleFilterStreamBehavior(  
 IAuthService authService,  
 ILogger<SampleFilterStreamBehavior<TRequest, TResponse>> logger  
 )  
    {  
        _logger = logger;  
        _authService = authService;  
    }  
  
    public async IAsyncEnumerable<TResponse> Handle(  
 TRequest request,  
 StreamHandlerDelegate<TResponse> next,  
 [EnumeratorCancellation] CancellationToken cancellationToken  
 )  
    {  
        await foreach (  
            var response in next().WithCancellation(cancellationToken).ConfigureAwait(false)  
        )  
        {  
            var isAllowed = _authService.OperationAlowed();  
  
            if (isAllowed.IsSuccess)  
            {  
                yield return response;  
            }  
            else  
            {  
                _logger.LogWarning(  
                    "User is not allowed to get this data, entity {json} has not be returned.",  
                    System.Text.Json.JsonSerializer.Serialize(response)  
                );  
            }  
        }  
    }  
}

注册

在域项目中,您将找到一个注册所有依赖项的扩展方法。

services.AddTransient(  
      typeof(IStreamPipelineBehavior<,>),  
      typeof(GenericStreamLoggingBehavior<,>)  
  );  
  services.AddTransient(  
      typeof(IStreamPipelineBehavior<,>),  
      typeof(SampleFilterStreamBehavior<,>)  
  );

通过分享这些见解,我希望创建一个有价值的资源,帮助提高 .NET 开发的能力。

源代码获取:公众号回复消息【code:15528

如果你喜欢我的文章,请给我一个赞!谢谢

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值