当前位置: 首页 > ops >正文

【ASP.NET Core】ASP.NET Core中间件解析

文章目录

  • 一、何为中间件
  • 二、请求管道中的中间件
    • 2.1 请求管道中的中间件执行顺序
    • 2.2 请求委托
      • 2.2.1 请求委托的本质
      • 2.2.2 请求委托与中间件
  • 三、中间件的定义方式
    • 3.1 内联中间件(in-line middleware)
    • 3.2 类中间件(class middleware)
    • 3.3 工厂模式中间件(factory-based middleware)
  • 四、中间件与过滤器


一、何为中间件

中间件是通过组装到应用程序管道中以处理请求和响应,它们按顺序组成一个请求管道。当客户端发送HTTP请求到应用程序时,请求会依次经过管道中的中间件进行处理;当服务器生成响应后,响应会按相反的顺序再次经过这些中间件,最终返回给客户端。

每个中间件都包含两大特性:

  • 选择是否将请求传递给管道中的下一个组件。
  • 在下一个组件之前和之后执行特定的逻辑。

每个中间件本身是一个处理当前请求的逻辑的请求委托(Request Delegate),它通过接收下一个中间件的请求委托,实现请求的传递。整个中间件管道,本质是由一系列嵌套的请求委托组成的。

也就是说中间件的本质是一个嵌套的请求委托容器,通过接收下一个中间件的请求委托,返回当前中间件的请求委托。其中这个返回的请求委托中包含了对当前逻辑的执行和对next的调用。多个中间件按注册顺序组成请求管道,请求按注册顺序流经每个节点,响应则按相反顺序回流

二、请求管道中的中间件

2.1 请求管道中的中间件执行顺序

请求管道采用链式处理流程。执行流程如下图所示
在这里插入图片描述
当客户端发送HTTP请求到服务器时,请求会按顺序流经管道中的每个中间件,中间件在管道中的注册顺序决定了执行顺序。先注册的中间件先处理请求,后处理响应;

中间件可以在请求进入时和响应返回时分别执行逻辑。也可以在在特定情况下不调用next(),短路请求。

每个中间件可以:

  • 对请求进行处理,执行逻辑;
  • 决定是否将请求传递给下一个中间件,调用.next()执行下一个中间件,否则就短路后续的中间件执行;
  • 在响应返回时执行额外逻辑;

管道的最后一个终端中间件会生成响应,响应再按相反顺序流经之前的中间件,最终返回给客户端。

2.2 请求委托

前面聊过中间件的本质是接收下一个中间件的请求委托,返回当前中间件的请求委托。其中这个返回的请求委托中包含了对当前逻辑的执行和对next的调用。

2.2.1 请求委托的本质

请求委托是一个预定义的委托类型,本质上是一个可执行的函数,接收HttpContext,返回Task。其签名如下。

/// <summary>
/// A function that can process an HTTP request.
/// </summary>
/// <param name="context">The <see cref="HttpContext"/> for the request.</param>
/// <returns>A task that represents the completion of request processing.</returns>
public delegate Task RequestDelegate(HttpContext context);

中间件正是通过请求委托的形式来定义自己的处理逻辑,来对HTTP请求进行各种处理。也就是说中间件的核心逻辑最终会被包装成一个请求委托,然后被加入请求处理管道中。

每个中间件在注册时,会接收下一个中间件的请求委托,并返回一个包含自身逻辑的请求委托。

整个管道本质是一系列嵌套的请求委托:前一个中间件的委托中包含对后一个中间件委托的调用。

下面用一个具体的内联中间件为例。

  1. app.Map用来定义一个管道可以处理哪些请求
  2. appbuilder.Use()的参数是一个 接收next并返回请求委托的函数,这正是中间件的核心定义方式。
  3. 中间件1返回的请求委托中,包含了自身逻辑和对中间件2的请求委托(next)的调用。直到最后的终端中间件,无next调用,到达管道终点。

Program.cs定义一个内联中间件管道

app.Map("/test", async appbuilder =>
{// 中间件1:返回一个请求委托,内部包含对next(中间件2的委托)的调用appbuilder.Use(async (context, next) =>{context.Response.ContentType = "text/html";await context.Response.WriteAsync("<h1>Middleware 1 : Hello World</h1>");await next.Invoke();await context.Response.WriteAsync("<h1>Middleware 1 : Goodbye World</h1>");});// 中间件2:返回一个请求委托,内部包含对next(中间件3的委托)的调用appbuilder.Use(async (context, next) =>{await context.Response.WriteAsync("<h1>Middleware 2 : Hello World</h1>");await next.Invoke();await context.Response.WriteAsync("<h1>Middleware 2 : Goodbye World</h1>");});// 中间件3:终端中间件,无next(管道终点)appbuilder.Run(async context =>{await context.Response.WriteAsync("Run<Br/>");});
});

当一个 HTTP 请求到达时,执行顺序如下:

  • 请求进入管道,先执行中间件 1 的 “处理请求前” 逻辑;
  • 中间件 1 通过 await next() 调用中间件 2 的 请求委托,请求传递给中间件 2;
  • 中间件 2 执行 “处理请求前” 逻辑,再通过 await next() 调用终端中间件的 请求委托;
  • 终端中间件处理请求,输出 “Run< Br/>”;
  • 响应开始反向返回:终端中间件执行完毕后,回到中间件 2,执行 “处理响应后” 逻辑;
  • 继续回到中间件 1,执行 “处理响应后” 逻辑;
  • 整个管道执行完毕,响应返回给客户端

2.2.2 请求委托与中间件

当一个HTTP请求到达时请求管道时,从第一个中间件的请求委托开始执行,执行到await next()时,触发下一个中间件的请求委托。依次重复,直到执行到终端中间件后,按相反顺序执行每个中间件中await next()之后的逻辑,生成响应返回。

在这个执行流程中,中间件的执行逻辑通过请求委托得以执行,而请求委托通过中间件定义好的请求委托的嵌套关系,按照指定的顺序执行。

中间件是处理HTTP请求和响应的一系列功能组件,它们通过请求委托构成执行链,每个中间件的逻辑通过请求委托的形式嵌入管道,实现请求的依次处理。

三、中间件的定义方式

中间件使用Run,Map和Use这三个扩展方法来配置请求委托。

  1. Map的核心作用是根据请求路径创建分支管道,一个请求管道可能有多个分支管道。当请求路径匹配 Map定义的路径(比如接下里示例里的 /test)时,会进入该分支管道处理;不匹配则走主管道。
app.Map("/test", async appbuilder =>
{appbuilder.Use(async (context, next) =>{context.Response.ContentType = "text/html";await context.Response.WriteAsync("<h1>Middleware 1 : Hello World</h1>");await next.Invoke();await context.Response.WriteAsync("<h1>Middleware 1 : Goodbye World</h1>");});
});app.Use(async (context, next) =>
{await context.Response.WriteAsync("<h1>Middleware 2 : Hello World</h1>");await next.Invoke();await context.Response.WriteAsync("<h1>Middleware 2 : Goodbye World</h1>");
});
  1. Use是注册中间件的核心方法,其作用是将中间件加入管道,并通过next.Invoke()调用下一个中间件的连接整个管道。
  2. Run的作用是注册终端中间件,本质是Use的简化版,内部不包含next 参数会终止此管道,放在管道或分支管道的最末端。

ASP.NET Core中,主要通过内联中间件(in-line middleware),类中间件(class middleware)和工厂模式中间件(factory-based middleware)这三种方式定义中间件。

3.1 内联中间件(in-line middleware)

内联中间件写法如下,通过Map指定匹配路径,通过Use将中间件加入管道,最后通过Run定义终端中间件。
Program.cs

app.Map("/test", async appbuilder =>
{// 中间件1:返回一个请求委托,内部包含对next(中间件2的委托)的调用appbuilder.Use(async (context, next) =>{context.Response.ContentType = "text/html";await context.Response.WriteAsync("<h1>Middleware 1 : Hello World</h1>");await next.Invoke();await context.Response.WriteAsync("<h1>Middleware 1 : Goodbye World</h1>");});// 中间件2:返回一个请求委托,内部包含对next(中间件3的委托)的调用appbuilder.Use(async (context, next) =>{await context.Response.WriteAsync("<h1>Middleware 2 : Hello World</h1>");await next.Invoke();await context.Response.WriteAsync("<h1>Middleware 2 : Goodbye World</h1>");});// 中间件3:终端中间件,无next(管道终点)appbuilder.Run(async context =>{await context.Response.WriteAsync("Run<Br/>");});
});

3.2 类中间件(class middleware)

将中间件逻辑封装到单独的类中实现复用。
类中间件的写法固定,包含一个公共构造函数,参数为RequestDelegate,这个请求委托便是下一个中间件的委托;
另外它包含一个公共的InvokeAsync方法,参数为HttpContext,返回Task。这个便是当前中间件的核心执行逻辑。
InvokeAsync方法中通过await _next.Invoke(context)区分中间件前逻辑和后逻辑。
类中间件

public class CustomMiddleware
{private readonly RequestDelegate _next;public CustomMiddleware(RequestDelegate next){_next = next;}public async Task InvokeAsync(HttpContext context){await context.Response.WriteAsync("<h1>CustomMiddleware : Hello World</h1>");await _next.Invoke(context);await context.Response.WriteAsync("<h1>CustomMiddleware : Goodbye World</h1>");}
}

Program.cs

app.UseMiddleware<CustomMiddleware>();

这种方式注册到容器里的中间件的生命周期是Singleton,也就是说整个应用生命周期内只创建一个实例。而中间件类本身的生命周期由其构造函数依赖的服务决定,中间件的生命周期与管道一致(应用启动时创建,直到应用停止)

3.3 工厂模式中间件(factory-based middleware)

通过工程模式构建的中间件可以指定其生命周期。需要实现IMiddleware接口,在Program.cs中往由DI容器注入的时候可以指定其生命周期。
工厂模式中间件

public class ScopedLoggingMiddleware : IMiddleware
{private readonly ILogger<ScopedLoggingMiddleware> _logger;private readonly Guid _instanceId = Guid.NewGuid(); // 每次实例化生成唯一ID// 构造函数:接收依赖注入的服务public ScopedLoggingMiddleware(ILogger<ScopedLoggingMiddleware> logger){_logger = logger;}// 实现接口方法:处理请求public async Task InvokeAsync(HttpContext context, RequestDelegate next){_logger.LogInformation($"中间件实例 {_instanceId} 处理请求:{context.Request.Path}");await next(context);}
}

Program.cs

builder.Services.AddScoped<ScopedLoggingMiddleware>();
var app = builder.Build();
appbuilder.UseMiddleware<ScopedLoggingMiddleware>();
app.Run();

四、中间件与过滤器

中间件与过滤器都是实现AOP的重要机制,两者的执行逻辑有些类似,但面对具体功能时使用二者,我们要考虑好当前逻辑执行的时机。

中间件是ASP.NET Core 的底层基础设施,ASP.NET Core 框架的核心组成部分,属于最基础的请求处理机制,不依赖于特定框架。无论是 MVC、Web API、Razor Pages,还是纯中间件应用,都依赖中间件管道处理请求。

而过滤器仅存在于 MVC/Web API 的生命周期中,是 MVC 框架为了处理 “控制器动作执行” 这一特定场景设计的扩展点。它的运行依赖于 MVC 框架的控制器激活、模型绑定等内部流程,无法脱离 MVC 单独在中间件管道中使用。

也就是说过滤器面向与action的执行,而中间件面向的是整个http请求。

http://www.xdnf.cn/news/18143.html

相关文章:

  • 如何安全删除GitHub中的敏感文件?git-filter-repo操作全解析
  • PowerBI VS FineBI VS QuickBI实现帕累托分析
  • [WiFi]RealTek RF MP Tool操作说明(RTL8192ES)
  • 编排之神--Kubernetes中的认证授权详解
  • PyTorch数据加载利器:torch.utils.data 详解与实践
  • RNN深层困境:残差无效,Transformer为何能深层?
  • 【RustFS干货】RustFS的智能路由算法与其他分布式存储系统(如Ceph)的路由方案相比有哪些独特优势?
  • MySQL深分页性能优化实战:大数据量情况下如何进行优化
  • 阿里云参数配置化
  • C++入门自学Day14-- deque类型使用和介绍(初识)
  • 私有化部署全攻略:开源模型本地化改造的性能与安全评测
  • IPD流程执行检查表
  • 消费者API
  • Flink on Native K8S安装部署
  • 软件系统运维常见问题
  • 快手可灵招海外产品运营实习生
  • 51单片机拼接板(开发板积木)
  • 计算机毕设推荐:痴呆症预测可视化系统Hadoop+Spark+Vue技术栈详解
  • MySQL事务篇-事务概念、并发事务问题、隔离级别
  • Vibe 编码技巧与建议(Vibe Coding Tips and Tricks)
  • AAA服务器技术
  • Qt中使用QString显示平方符号(如²)
  • 搭建最新--若依分布式spring cloudv3.6.6 前后端分离项目--步骤与记录常见的坑
  • 【qml-5】qml与c++交互(类型单例)
  • 前端下载文件、压缩包
  • Java网络编程:TCP与UDP通信实现及网络编程基础
  • 集成电路学习:什么是Object Tracking目标跟踪
  • 大模型参数如何影响模型的学习和优化?
  • 从H.264到AV1:音视频技术演进与模块化SDK架构全解析
  • 开源游戏引擎Bevy 和 Godot