最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
ASP.NET Core中间件用法与官方常用中间件介绍
时间:2026-09-17 13:00:02 编辑:袖梨 来源:一聚教程网
一、什么是中间件
我们都知道,任何的一个web框架都是把http请求封装成一个管道,每一次的请求都是经过管道的一系列操作,最终才会到达我们写的代码中。而中间件就是用于组成应用程序管道来处理请求和响应的组件。管道内的每一个组件都可以选择是否将请求转交给下一个组件,并在管道中调用下一个组件之前和之后执行某些操作。请求委托被用来建立请求管道,请求委托处理每一个HTTP请求。
中间件可以认为有两个基本的职责:
选择是否将请求传递给管道中的下一个中间件。可以在管道中的下一个中间件前后执行一些工作。请求委托通过使用IApplicationBuilder类型的Run、Map以及Use扩展方法来配置,并在Startup类中传给Configure方法。每个单独的请求委托都可以被指定为一个内嵌匿名方法,或其定义在一个可重用的类中。这些可以重用的类被称作“中间件”或“中间件组件”。每个位于请求管道内的中间件组件负责调用管道中下一个组件,或适时短路调用链。中间件是一个典型的AOP应用。
ASP.NET Core请求管道由一系列的请求委托所构成,它们一个接着一个的被调用,看下面一张微软官方的中间件请求管道图(图中执行线程按黑色箭头的顺序执行):

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

在上图中,我们可以把中间件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()方法的定义:

中定义中可以看出: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");});
程序运行结果:

可以看到:只输出了中间件1的信息,没有输出中间件2的信息,说明发生了短路。
注意:Run()方法被称为终端中间件,要放在所有中间件的最后面,否则在Run()方法后面的中间件将不会被执行。
2、Use方法
我们先来看看Use()方法的定义:

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

可以看出: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();});
程序运行结果:

我们在上面说过,可以在调用中间件之前和之后做一些工作,看下面的代码:
// 向应用程序的请求管道中添加一个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");});
程序运行结果:

我们可以总结上面代码的执行顺序:
请求先到达中间件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");});
程序运行结果:

可以看出:如果使用Use()方法,不调用next,实现的效果跟使用Run()方法一样,都会使管道发生短路。
3、Map方法
Map作为惯例,将管道分流。Map根据给定请求路径匹配将请求管道分流。如果请求路径以指定路径开始,则执行分支。看一下Map()方法的定义:

可以看到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,输出结果:

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

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输出结果:

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

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

Map也可以同时匹配多个段,看下面的代码:
运行程序,输出结果:

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

4、Mapwhen方法
MapWhen是基于给定的谓词分支请求管道。任何使Func<HttpContext,bool>返回true的谓词的请求都被映射到新的管道分支。
我们先来看看Mapwhen方法的定义:

可以看出: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");});
运行程序,输出结果:

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

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

三、自定义中间件
在上面的例子中,我们都是使用的官方中间件自动的方法,其实我们也可以自己编写一个中间件。
中间件遵循显示依赖原则,并在其构造函数中暴露所有依赖项。中间件能够利用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();
运行程序,查看结果:

这样就完成了一个自定义中间件。
四、官方常用中间件
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中间件用法与官方常用中间件的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持一聚教程网。
相关文章
- Linux下使用Jenkins自动化构建.NET Core应用 09-17
- .NET中的字符串驻留池介绍 09-17
- 使用Docker部署ASP.NET Core程序 09-17
- ASP.NET Core中间件用法与官方常用中间件介绍 09-17
- 部署ASP.NET Core程序到Linux系统 09-17
- ASP.NETM5C中_5iewStart.cshtml作用介绍 09-17