ASP.NET Core Middleware抽丝剥茧

简介: ASP.NET Core Middleware是在ASP.NET Core处理管道中处理特定业务逻辑的组件。

一. 中间件的概念和数据结构


ASP.NET Core Middleware是在ASP.NET Core处理管道中处理特定业务逻辑的组件。


ASP.NET Core处理管道由一系列请求委托组成,一环接一环的调用特定的中间件。


6b23fb53af011d6e3ad3659a959062f2.png


上图示例:


处理管道包含四个中间件,每个中间件都包含后续中间件执行动作的引用(next),同时每个中间件在交棒之前和交棒之后可以自行参与针对HttpContxt的业务处理。


通过上面的分析,中间件其实具备两个特征:


  • 入参:下一个中间件的执行委托RequestDelegate  (public delegate Task RequestDelegate(HttpContext context);)


  • 输出:特定中间件的业务处理动作:因为中间件是处理管道中预设的处理逻辑,所以这个动作其实也是一个委托RequestDelegate


所以.NET Core用Func<RequestDelegate,RequestDelegate> 数据结构表示中间件是合理的。


二. Middleware的定义方式


ASP.NETCore 提供了很多内置的中间件,帮助我们完成基础通用的业务逻辑。


有两种自定义中间件的方式:


1. Factory-based Middleware


基于工厂模式的中间件有如下优点:


  • 在每个客户端请求时激活实例 (injection of scoped services)


  • 实现IMiddleware接口: 强类型


//  该接口只有一个固定的函数
public Task InvokeAsync(HttpContext context,RequestDelegate next);


public class FactoryActivatedMiddleware : IMiddleware
    {
        private readonly ILogger _logger;
        public FactoryActivatedMiddleware(ILoggerFactory logger)   // 
        {
            _logger = logger.CreateLogger<FactoryActivatedMiddleware>();
        }
        public async Task InvokeAsync(HttpContext context, RequestDelegate next)
        {
            // TODO  logic handler
            _logger.LogInformation("测试");
            await next(context);
            // TODO  logic handler
        }
    }


使用工厂模式的中间件,构造函数参数由依赖注入(DI)填充;


在[使用UseMiddleware()注册中间件]时不允许显式传参。


f6ebef6a5a1a764d29385bbc1362edaa.png



源码在https://github.com/dotnet/aspnetcore/blob/v5.0.1/src/Http/Http.Abstractions/src/Extensions/UseMiddlewareExtensions.cs第56行


2. Conventional-based Middleware


顾名思义,基于约定的中间件类有一些特定约定:


  • 具有RequestDelegate类型参数的公共构造函数


  • 名称为Invoke或InvokeAsync的公共方法, 此方法必须①返回Task   ② 方法第一个参数是HttpContext


public class ConventionalMiddleware
{
    private readonly RequestDelegate _next;
    public ConventionalMiddleware(RequestDelegate next)
    {
        _next = next;
    }
    public async Task InvokeAsync(HttpContext context, AppDbContext db)
    {
        var keyValue = context.Request.Query["key"];
        if (!string.IsNullOrWhiteSpace(keyValue))
        {
            db.Add(new Request()
                {
                    DT = DateTime.UtcNow, 
                    MiddlewareActivation = "ConventionalMiddleware", 
                    Value = keyValue
                });
            await db.SaveChangesAsync();
        }
        await _next(context);
    }
}


构造函数和Invoke/InvokeAsync的其他参数由依赖注入(DI)填充;


基于约定的中间件,在[使用UseMiddleware()注册中间件]时允许显式传参。


三. 注册中间件的算法分析


app.UseMiddleware<TMiddleware>()内部使用app.Use(Func<RequestDelegate,RequestDelegate> middleware)注册中间件,

返回值还是IApplicationBuilder, 故具备链式注册的能力。


算法类似于基础链表, 只是指针指向的不是节点,而是后续节点的字段。


dab7cc8db83ff4cbd875c1b2471599c8.png


//--------节选自 Microsoft.AspNetCore.Builder.Internal.ApplicationBuilder--------
private readonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>();
public IApplicationBuilder Use(Func<RequestDelegate,RequestDelegate> middleware)
{
    this._components.Add(middleware);
    return this;
}
public RequestDelegate Build()
{
        RequestDelegate app = context =>
            {
                // If we reach the end of the pipeline, but we have an endpoint, then something unexpected has happened.
                // This could happen if user code sets an endpoint, but they forgot to add the UseEndpoint middleware.
                var endpoint = context.GetEndpoint();
                var endpointRequestDelegate = endpoint?.RequestDelegate;
                if (endpointRequestDelegate != null)
                {
                    var message =
                        $"The request reached the end of the pipeline without executing the endpoint: '{endpoint!.DisplayName}'. " +
                        $"Please register the EndpointMiddleware using '{nameof(IApplicationBuilder)}.UseEndpoints(...)' if using " +
                        $"routing.";
                    throw new InvalidOperationException(message);
                }
                context.Response.StatusCode = StatusCodes.Status404NotFound;
                return Task.CompletedTask;
            };
            foreach (var component in _components.Reverse())
            {
                app = component(app);
            }
            return app;
}


通过以上代码我们可以看出:


  • 注册中间件的过程实际上,是给一个Type为List<Func<RequestDelegate, RequestDelegate>> 的集合依次添加元素的过程;


  • 中间件的数据结构:(input)后置中间件的执行函数指针(以委托RequestDelegate表示),(output)当前中间件的执行函数指针(以委托RequestDelegate表示);


  • 通过build方法将集合打通为链表,链表就是我们常说的[处理管道]。


build方法是在web托管服务GenericWebHostService开始启动的时候被调用。源码在https://github.com/dotnet/aspnetcore/blob/master/src/Hosting/Hosting/src/GenericHost/GenericWebHostedService.cs 105 行。


附:非标准中间件的用法


短路中间件、 分叉中间件、条件中间件


整个处理管道的形成,存在一些管道分叉或者临时插入中间件的行为,一些重要方法可供使用


  • Use方法是一个注册中间件的简便写法


  • Run方法是一个约定,一些中间件使用Run方法来完成管道的结尾


  • Map扩展方法:Path满足指定条件,将会执行分叉管道


  • MapWhen方法:HttpContext满足条件,将会执行分叉管道,相比Map有更灵活的匹配功能


  • UseWhen方法:HttpContext满足条件则插入中间件



78a103e3517ea9c78e630039f4b38632.png

相关文章
|
15天前
|
开发框架 .NET 开发者
简化 ASP.NET Core 依赖注入(DI)注册-Scrutor
Scrutor 是一个简化 ASP.NET Core 应用程序中依赖注入(DI)注册过程的开源库,支持自动扫描和注册服务。通过简单的配置,开发者可以轻松地从指定程序集中筛选、注册服务,并设置其生命周期,同时支持服务装饰等高级功能。适用于大型项目,提高代码的可维护性和简洁性。仓库地址:&lt;https://github.com/khellang/Scrutor&gt;
37 5
|
1月前
|
开发框架 .NET C#
在 ASP.NET Core 中创建 gRPC 客户端和服务器
本文介绍了如何使用 gRPC 框架搭建一个简单的“Hello World”示例。首先创建了一个名为 GrpcDemo 的解决方案,其中包含一个 gRPC 服务端项目 GrpcServer 和一个客户端项目 GrpcClient。服务端通过定义 `greeter.proto` 文件中的服务和消息类型,实现了一个简单的问候服务 `GreeterService`。客户端则通过 gRPC 客户端库连接到服务端并调用其 `SayHello` 方法,展示了 gRPC 在 C# 中的基本使用方法。
42 5
在 ASP.NET Core 中创建 gRPC 客户端和服务器
|
23天前
|
开发框架 缓存 .NET
GraphQL 与 ASP.NET Core 集成:从入门到精通
本文详细介绍了如何在ASP.NET Core中集成GraphQL,包括安装必要的NuGet包、创建GraphQL Schema、配置GraphQL服务等步骤。同时,文章还探讨了常见问题及其解决方法,如处理复杂查询、错误处理、性能优化和实现认证授权等,旨在帮助开发者构建灵活且高效的API。
26 3
|
2月前
|
开发框架 JavaScript 前端开发
一个适用于 ASP.NET Core 的轻量级插件框架
一个适用于 ASP.NET Core 的轻量级插件框架
|
7月前
|
消息中间件 存储 负载均衡
消息中间件的选择:RabbitMQ是一个明智的选择
消息中间件的选择:RabbitMQ是一个明智的选择
120 0
|
6月前
|
消息中间件 存储 中间件
【消息中间件】详解三大MQ:RabbitMQ、RocketMQ、Kafka
【消息中间件】详解三大MQ:RabbitMQ、RocketMQ、Kafka
1744 0
|
5月前
|
消息中间件 编解码 Docker
Docker部署RabbitMQ消息中间件
【7月更文挑战第4天】Docker部署RabbitMQ消息中间件
285 3
|
2月前
|
消息中间件 编解码 Docker
【Docker项目实战】Docker部署RabbitMQ消息中间件
【10月更文挑战第8天】Docker部署RabbitMQ消息中间件
128 1
【Docker项目实战】Docker部署RabbitMQ消息中间件
|
4月前
|
消息中间件 Java 测试技术
消息中间件RabbitMQ---SpringBoot整合RabbitMQ【三】
这篇文章是关于如何在SpringBoot应用中整合RabbitMQ的消息中间件。内容包括了在SpringBoot项目中添加RabbitMQ的依赖、配置文件设置、启动类注解,以及如何通过单元测试来创建交换器、队列、绑定,并发送和接收消息。文章还介绍了如何配置消息转换器以支持对象的序列化和反序列化,以及如何使用注解`@RabbitListener`来接收消息。
消息中间件RabbitMQ---SpringBoot整合RabbitMQ【三】
|
4月前
|
消息中间件 Docker 容器
消息中间件RabbitMQ---Docker安装RabbitMQ、以及RabbitMQ的基本使用【二】
这篇文章提供了RabbitMQ的安装和基本使用教程,包括如何使用Docker拉取RabbitMQ镜像、创建容器、通过浏览器访问管理界面,以及如何创建交换机、队列、绑定和使用direct、fanout和topic三种类型的交换器进行消息发布和接收的测试。
消息中间件RabbitMQ---Docker安装RabbitMQ、以及RabbitMQ的基本使用【二】