ASP.NET Core中间件用法与官方常用中间件介绍

作者:袖梨 2026-09-17
这篇文章介绍了ASP.NET Core中间件用法与官方常用中间件,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

一、什么是中间件

我们都知道,任何的一个web框架都是把http请求封装成一个管道,每一次的请求都是经过管道的一系列操作,最终才会到达我们写的代码中。而中间件就是用于组成应用程序管道来处理请求和响应的组件。管道内的每一个组件都可以选择是否将请求转交给下一个组件,并在管道中调用下一个组件之前和之后执行某些操作。请求委托被用来建立请求管道,请求委托处理每一个HTTP请求。

中间件可以认为有两个基本的职责:

选择是否将请求传递给管道中的下一个中间件。可以在管道中的下一个中间件前后执行一些工作。

请求委托通过使用IApplicationBuilder类型的Run、Map以及Use扩展方法来配置,并在Startup类中传给Configure方法。每个单独的请求委托都可以被指定为一个内嵌匿名方法,或其定义在一个可重用的类中。这些可以重用的类被称作“中间件”或“中间件组件”。每个位于请求管道内的中间件组件负责调用管道中下一个组件,或适时短路调用链。中间件是一个典型的AOP应用。

ASP.NET Core请求管道由一系列的请求委托所构成,它们一个接着一个的被调用,看下面一张微软官方的中间件请求管道图(图中执行线程按黑色箭头的顺序执行):

img_64cc99804625230.jpg

中间件短路:每一个委托在下一个委托之前和之后都有机会执行操作。任何委托都能选择停止传递到下一个委托,而是结束请求并开始响应,这就是请求管道的短路,这是一种有意义的设计,因为它可以避免一些不必要的工作。比如说,一个授权(authorization)中间件只有在通过身份验证之后才能调用下一个委托,否则它就会被短路,并返回“Not Authorized”的响应。异常处理委托需要在管道的早期被调用,这样它们就能够捕捉到发生在管道内更深层次出现的异常了。短路可以用下面这张图来表示:

img_64cc998055af231.jpg

在上图中,我们可以把中间件1认为是身份认证的中间件,HTTP请求发送过来,首先经过身份认证中间件,如果身份认证失败,那么就直接给出响应并返回,不会再把请求传递给下面的中间件2和中间件3.

中间件的执行跟调用的顺序有关,然后在响应时则以相反的顺序返回。

请求在每一步都可能被短路,所以我们要以正确的顺序添加中间件,如异常处理中间件,我们要添加在最开始的地方,这样就能第一时间捕获异常,以及后续中间可能发生的异常,然后最终做处理返回。

我们来看看Configure方法里面提供了哪些中间件:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env){if (env.IsDevelopment()){// 异常中间件app.UseDeveloperExceptionPage();}// 路由中间件app.UseRouting();// 授权中间件app.UseAuthorization();// 终结点中间件app.UseEndpoints(endpoints =>{endpoints.MapControllers();});}

中间件和过滤器的区别

中间件和过滤器都是一种AOP的思想,他们的功能类似,那么他们有什么区别呢?

过滤器更加贴合业务,它关注于应用程序本身,关注的是如何实现业务,比如对输出结果进行格式化,对请求的ViewModel进行数据校验,这时就肯定要使用过滤器了。过滤器是MVC的一部分,它可以拦截到你Action上下文的一些信息,而中间件是没有这个能力的。可以认为过滤器是附加性的一种功能,它只是中间件附带表现出来的特征。中间件是管道模型里重要的组成部分,不可或缺,而过滤器可以没有。

二、中间件常用方法

中间件中定义了Run、Use、Map、MapWhen几种方法,我们下面一一讲解这几种方法。

1、Run方法

我们先来看到Run()方法的定义:

img_64cc99806a37132.jpg

中定义中可以看出:Run()方法中只有一个RequestDelegate委托类型的参数,没有Next参数,所以Run()方法也叫终端中间件,不会将请求传递给下一个中间件,也就是发生了“短路”。看下面的代码:

// Run方法向应用程序的请求管道中添加一个RequestDelegate委托// 放在管道最后面,终端中间件app.Run(handler: async context => {await context.Response.WriteAsync(text: "Hello World1rn");});app.Run(handler: async context =>{await context.Response.WriteAsync(text: "Hello World2rn");});

程序运行结果:

img_64cc9980839c033.jpg

可以看到:只输出了中间件1的信息,没有输出中间件2的信息,说明发生了短路。

注意:Run()方法被称为终端中间件,要放在所有中间件的最后面,否则在Run()方法后面的中间件将不会被执行。

2、Use方法

我们先来看看Use()方法的定义:

img_64cc998092b5534.jpg

可以看出:Use方法的参数是一个Func委托,输入参数是一个RequestDelegate类型的委托,返回参数也是一个RequestDelegate类型的委托,这里表示调用下一个中间件,我们在来看看RequestDelegate委托的定义:

img_64cc9980aa73535.jpg

可以看出:RequestDelegate是一个委托,有一个HttpContext类型的参数,HttPContext表示Http请求上下文,可以获取请求信息,返回值是Task类型,明白了Use()方法的参数以后,我们写一个自定义的Use()方法:

// 向应用程序的请求管道中添加一个Func委托,这个委托其实就是所谓的中间件。// context参数是HttpContext,表示HTTP请求的上下文对象// next参数表示管道中的下一个中间件委托,如果不调用next,则会使管道短路// 用Use可以将多个中间件链接在一起app.Use(async (context, next) =>{await context.Response.WriteAsync(text: "hello Use1rn");// 调用下一个委托await next();});app.Use(async (context, next) =>{await context.Response.WriteAsync(text: "hello Use2rn");// 调用下一个委托await next();});

程序运行结果:

img_64cc9980c362636.jpg

我们在上面说过,可以在调用中间件之前和之后做一些工作,看下面的代码:

// 向应用程序的请求管道中添加一个Func委托,这个委托其实就是所谓的中间件。// context参数是HttpContext,表示HTTP请求的上下文对象// next参数表示管道中的下一个中间件委托,如果不调用next,则会使管道短路// 用Use可以将多个中间件链接在一起app.Use(async (context, next) =>{// 解决中文乱码问题context.Response.ContentType = "text/plain; charset=utf-8";await context.Response.WriteAsync(text: "中间件1:传入请求rn");// 调用下一个委托await next();await context.Response.WriteAsync(text: "中间件1:传出响应rn");});app.Use(async (context, next) =>{await context.Response.WriteAsync(text: "中间件2:传入请求rn");// 调用下一个委托await next();await context.Response.WriteAsync(text: "中间件2:传出响应rn");});app.Run(handler:async context =>{await context.Response.WriteAsync(text: "中间件3:处理请求并生成响应rn");});

程序运行结果:

img_64cc99810569e37.jpg

我们可以总结上面代码的执行顺序:

请求先到达中间件1,然后输出(中间件1:传入请求)然后中间件1调用next()。next()会调用管道中的中间件2。中间件2输出(中间件2:传入请求)。然后中间件2会调用next()。next()在调用管道中的中间件3。中间件3处理请求并生成响应,不在调用下一个中间件,所以我们看到输出(中间件3:处理请求并生成响应)。这时管理开始发生逆转。此时控制器将交回到中间件2,并将中间件3生成的响应传递给它。中间件2输出(中间件2:传出响应)。最后,中间件2在将控制权交给中间件1。中间件1最后输出(中间件1:传出响应),这就是我们最后看的的结果。

我们知道:Use()方法中有两个参数,next参数表示调用管道中的下一个中间件,如果不调用next,那么也会使管道发生短路,相当于Run()方法,看下面的代码:

// 向应用程序的请求管道中添加一个Func委托,这个委托其实就是所谓的中间件。// context参数是HttpContext,表示HTTP请求的上下文对象// next参数表示管道中的下一个中间件委托,如果不调用next,则会使管道短路// 用Use可以将多个中间件链接在一起app.Use(async (context, next) =>{// 解决中文乱码问题context.Response.ContentType = "text/plain; charset=utf-8";await context.Response.WriteAsync(text: "中间件1:传入请求rn");// 调用下一个委托await next();await context.Response.WriteAsync(text: "中间件1:传出响应rn");});app.Use(async (context, next) =>{await context.Response.WriteAsync(text: "中间件2:传入请求rn");// 调用下一个委托await next();await context.Response.WriteAsync(text: "中间件2:传出响应rn");});//app.Run(handler:async context =>//{//await context.Response.WriteAsync(text: "中间件3:处理请求并生成响应rn");//});// Use方法也可以不调用next,表示发生短路app.Use(async (context, next) =>{await context.Response.WriteAsync(text: "中间件3:处理请求并生成响应rn");});

程序运行结果:

img_64cc998129fc338.jpg

可以看出:如果使用Use()方法,不调用next,实现的效果跟使用Run()方法一样,都会使管道发生短路。

3、Map方法

Map作为惯例,将管道分流。Map根据给定请求路径匹配将请求管道分流。如果请求路径以指定路径开始,则执行分支。看一下Map()方法的定义:

img_64cc99813c19239.jpg

可以看到Map方法有两个参数:第一个参数是匹配规则,第二个参数是Action泛型委托,泛型委托参数是IApplicationBuilder类型,和Configure方法的第一个参数类型相同。这就表示可以把实现了Action泛型委托的方法添加到中间件管道中执行。

我们首先定义一个方法,该方法的参数是IApplicationBuilder类型:

/// <summary>/// 自定义方法/// </summary>/// <param name="app">IApplicationBuilder</param>private void HandleMap1(IApplicationBuilder app){app.Run(handler: async context => {await context.Response.WriteAsync(text: "Hello Map1");});}/// <summary>/// 自定义方法/// </summary>/// <param name="app">IApplicationBuilder</param>private void HandleMap2(IApplicationBuilder app){app.Run(handler: async context =>{await context.Response.WriteAsync(text: "Hello Map2");});}

然后看一下使用Map方法的代码:

// Map可以根据匹配的URL来选择执行,简单来说就是根据URL进行分支选择执行// 有点类似于MVC中的路由// 匹配的URL:http://l*ocalho**st:5000/Map1app.Map(pathMatch: "/Map1", configuration: HandleMap1);// 匹配的URL:http://loca*lh**ost:5000/Map2app.Map(pathMatch: "/Map2", configuration: HandleMap2);

运行程序,然后在浏览器地址栏里面输入:http://loca*l*ho*st:5000/Map1,输出结果:

img_64cc99814a659310.jpg

在地址栏里面在输入:http://*localh**ost:5000/Map2,输出结果:

img_64cc99816e1ad311.jpg

Map还支持嵌套,看下面的代码:

// 嵌套Mapapp.Map(pathMatch: "/Map1", configuration: App1 => {//App1.Map("/Map2",action=> {action.Run(async context => {await context.Response.WriteAsync("This is /Map1/Map2");});});App1.Run(async context => {await context.Response.WriteAsync("This is no-map");});});

访问http://l*oc*alhost*:5000/Map1/123输出结果:

img_64cc99818d2ab312.jpg

访问http://lo*ca**lhost:5000/Map1输出结果:

img_64cc99819645e313.jpg

访问http://loca*l*ho*st:5000/Map1/Map2输出结果:

img_64cc9981af20d314.jpg

Map也可以同时匹配多个段,看下面的代码:

运行程序,输出结果:

img_64cc9981c40fe315.jpg

访问http://loca*l*ho*st:5000/Map1/Map2输出结果:

img_64cc9981d41fb316.jpg

4、Mapwhen方法

MapWhen是基于给定的谓词分支请求管道。任何使Func<HttpContext,bool>返回true的谓词的请求都被映射到新的管道分支。

我们先来看看Mapwhen方法的定义:

img_64cc9981ea3ce317.jpg

可以看出:MapWhen方法有两个参数:第一个参数是Func类型的委托,输入参数是HttpContext,输出参数是bool类型。第二个参数是Action委托,参数是IApplicationBuilder类型,表示也可以把实现Action委托的方法添加到中间件管道中执行。

看下面的例子,如果url中包括name查询参数,则执行HandleName方法,如果包含age查询参数,则执行HandleAge方法,否则执行Run()方法。

HandleName和HandleAge方法定义如下:

private void HandleName(IApplicationBuilder app){app.Run(handler: async context =>{await context.Response.WriteAsync(text: $"This name is: {context.Request.Query["name"]}");});}private void HandleAge(IApplicationBuilder app){app.Run(handler: async context =>{await context.Response.WriteAsync(text: $"This age is: {context.Request.Query["age"]}");});}

对应的MapWhen方法定义如下:

// 如果访问的url参数中包含name,则执行HandleNameapp.MapWhen(// Func委托,输入参数是HttpContext,返回bool值predicate: context =>{// 判断url参数中是否包含namereturn context.Request.Query.ContainsKey("name");}, configuration: HandleName);// 如果访问的url参数中包含name,则执行HandleAgeapp.MapWhen(// Func委托,输入参数是HttpContext,返回bool值predicate: context =>{// 判断url参数中是否包含agereturn context.Request.Query.ContainsKey("age");}, configuration: HandleAge);app.Run(async context => {await context.Response.WriteAsync("There is non-Map delegate rn");});

运行程序,输出结果:

img_64cc9982068f7318.jpg

在url里面添加name查询参数输出结果:

img_64cc998219d35319.jpg

在url里面添加age查询参数输出结果:

img_64cc998234969320.jpg

三、自定义中间件

在上面的例子中,我们都是使用的官方中间件自动的方法,其实我们也可以自己编写一个中间件。

中间件遵循显示依赖原则,并在其构造函数中暴露所有依赖项。中间件能够利用UseMiddleware<T>扩展方法的优势,直接通过它们的构造函数注入服务。依赖注入服务是自动完成填充的。

ASP.NET Core约定中间件类必须包括以下内容:

具有类型为RequestDelegate参数的公共构造函数。必须有名为Invoke或InvokeAsync的公共方法,此方法必须满足两个条件:方法返回类型是Task、方法的第一个参数必须是HttpContext类型。

我们自定义一个记录IP的中间件,新建一个类RequestIPMiddleware,代码如下:

using Microsoft.AspNetCore.Http;using System.Threading.Tasks;namespace MiddlewareDemo.Middleware{/// <summary>/// 记录IP地址的中间件/// </summary>public class RequestIPMiddleware{// 私有字段private readonly RequestDelegate _next;/// <summary>/// 公共构造函数,参数是RequestDelegate类型/// 通过构造函数进行注入,依赖注入服务会自动完成注入/// </summary>/// <param name="next"></param>public RequestIPMiddleware(RequestDelegate next){_next = next;}/// <summary>/// Invoke方法/// 返回值是Task,参数类型是HttpContext/// </summary>/// <param name="context">Http上下文</param>/// <returns></returns>public async Task Invoke(HttpContext context){await context.Response.WriteAsync($"User IP:{context.Connection.RemoteIpAddress.ToString()}rn");// 调用管道中的下一个委托await _next.Invoke(context);}}}

然后创建一个扩展方法,对IApplicationBuilder进行扩展:

using Microsoft.AspNetCore.Builder;namespace MiddlewareDemo.Middleware{public static class RequestIPExtensions{/// <summary>/// 扩展方法,对IApplicationBuilder进行扩展/// </summary>/// <param name="builder"></param>/// <returns></returns>public static IApplicationBuilder UseRequestIP(this IApplicationBuilder builder){// UseMiddleware<T>return builder.UseMiddleware<RequestIPMiddleware>();}}}

最后在Startup类的Configure方法中使用自定义中间件:

// 使用自定义中间件app.UseRequestIP();

运行程序,查看结果:

img_64cc99824352f321.jpg

这样就完成了一个自定义中间件。

四、官方常用中间件

1、异常处理中间件

当应用程序在开发环境中运行时,开发人员异常页中间件(UseDeveloperExceptionPage)报告应用程序运行时的错误。

当应用程序在生产环境中运行时,异常处理中间件(UseExceptionHandler)捕获下面中间件中引发的异常。

2、HTTPS重定向中间件

HTTPS重定向中间件(UseHttpsRedirection)会将HTTP请求重定向到HTTPS。

3、静态文件中间件

静态文件中间件(UseStaticFiles)返回静态文件,并简化进一步请求处理。

4、Cookie中间件

Cookie策略中间件(UseCookiePolicy)使应用符合欧盟一般数据保护条例的规定。

5、路由中间件

路由中间件(UseRouting)用于路由的请求。

6、身份认证中间件

身份认证中间件(UseAuthentication)尝试对用户进行身份验证,验证通过之后才会允许用户访问安全资源。

7、授权中间件

授权中间件(UseAuthorization)用于授权验证通过的用户可以访问哪些资源。

8、会话中间件

会话中间件(UseSession)建立和维护会话状态。如果应用程序使用会话状态,请在Cookie策略中间件之后和MVC中间件之前调用会话中间件。

9、终结点路由中间件

终结点路由中间件(UseEndpoints)用于将Razor Pages终结点添加到请求管道。

更多中间件组件可以到aspnet 的GitHub仓库中查看:https://github*.c**om/aspnet。

示例代码GitHub地址:https://gi*thub*.*com/jxl1024/Middleware

到此这篇关于ASP.NET Core中间件用法与官方常用中间件的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持一聚教程网。

相关文章

精彩推荐