【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请求进行各种处理。也就是说中间件的核心逻辑最终会被包装成一个请求委托,然后被加入请求处理管道中。
每个中间件在注册时,会接收下一个中间件的请求委托,并返回一个包含自身逻辑的请求委托。
整个管道本质是一系列嵌套的请求委托:前一个中间件的委托中包含对后一个中间件委托的调用。
下面用一个具体的内联中间件为例。
- app.Map用来定义一个管道可以处理哪些请求
- appbuilder.Use()的参数是一个 接收next并返回请求委托的函数,这正是中间件的核心定义方式。
- 中间件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这三个扩展方法来配置请求委托。
- 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>");
});
- Use是注册中间件的核心方法,其作用是将中间件加入管道,并通过next.Invoke()调用下一个中间件的连接整个管道。
- 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请求。