ASP.NET Core 中间件执行机制详解:Use、Run、Map 与请求管道原理
深入解析 ASP.NET Core 中间件执行模型,从 Use、Run、Map 的区别入手,结合代码示例讲透请求管道的嵌套委托结构与“先进后出”的执行机制,帮助理解 next.Invoke 背后的真实原理。
中间件
中间件是 ASP.NET Core 的核心组件,例如响应缓存、用户身份验证、CORS、Swagger等重要的功能都是由 ASP.NET 内置的中间件提供的
注:这一篇主要讲解中间件的一些简单概念,下一篇文章讲解自定义中间件。
中间件的三个概念 Map、Use、Run
处理顺序
Map 用来定义一个管道可以处理哪些请求, Use 和 Run 用来定义管道, “一个管道” 由 “若干个Use” 和 “一个Run” 组成,
每个 Use 引入一个中间件,而 Run 用来执行最终的核心应用逻辑。
上代码
app.Map("/test", async appbuilder => {
appbuilder.Use(async (context, next) => {
context.Response.ContentType = "text/html;charset=utf-8";
await context.Response.WriteAsync("1 Start<br/>");
await next.Invoke();
await context.Response.WriteAsync("1 End<br/>");
});
appbuilder.Use(async (context, next) => {
await context.Response.WriteAsync("2 Start<br/>");
await next.Invoke();
await context.Response.WriteAsync("2 End<br/>");
});
appbuilder.Run(async context =>
{
await context.Response.WriteAsync("3 Run Start<br/>");
await context.Response.WriteAsync("3 Run End<br/>");
});
});
猜一下访问 /test 后的输出内容是什么(如图)?

这个顺序是不是和大家想的有点不一样。
为什么不是这个顺序
1 Start
1 End
2 Start
2 End
3 Run Start
3 Run End
因为中间件是分为前、后逻辑,而前后逻辑是由 next 分开的。
awit next.Invoke(); 把请求转到下一个中间件,即不会执行该语句后面的代码(后逻辑),
直接把请求转到下一个 “use” ;
直到遇到 “run” , " run " 负责业务规则且不再将请求向后传递,当 “run” 执行结束后,
响应会按照请求的相反顺序执行中间件中的后逻辑。
注:如果我们在一个中间件中使用 context.Response.WriteAsync 等方式向客户端发送响应,我们就尽量不再执行 next.Invoke 把请求转到其他中间件,
因为其他中间件中有可能对 Response 中进行了修改,比如修改响应状态码、报文头、向报文中写入其他数据,这样就会造成报文体被损坏的问题。
所以正常的请求处理路径是(如图):

中间件执行顺序是:先进后出(栈结构)
执行顺序
我们现在把第一个和第二个中间件交换一下顺序。
app.Map("/test", async appbuilder => {
appbuilder.Use(async (context, next) => {
context.Response.ContentType = "text/html;charset=utf-8";
await context.Response.WriteAsync("2 Start<br/>");
await next.Invoke();
await context.Response.WriteAsync("2 End<br/>");
});
appbuilder.Use(async (context, next) => {
await context.Response.WriteAsync("1 Start<br/>");
await next.Invoke();
await context.Response.WriteAsync("1 End<br/>");
});
appbuilder.Run(async context =>
{
await context.Response.WriteAsync("3 Run Start<br/>");
await context.Response.WriteAsync("3 Run End<br/>");
});
});
运行截图:

从图中我们可以看出中间件的注册顺序就是执行顺序。
然后再回到我们的代码,两个中间件除了交换了一下顺序,是否还有其他修改。
修改在这里
context.Response.ContentType = “text/html;charset=utf-8”;
回看 “处理顺序” 实验的代码部分。 这句代码在第一个中间件里面,现在中间件交换顺序后,还是在第一个中间件里面。
| 设置 | 效果 | 浏览器行为 |
|---|---|---|
| text/html; charset=utf-8 | 告诉浏览器:“这是 HTML,用 UTF-8 编码” | 正确渲染 HTML 标签,中文不乱码 |
| 不设置(默认) | 浏览器猜测内容类型 | 可能显示纯文本、乱码,或不渲染 |
为什么把context.Response.ContentType = “text/html;charset=utf-8”;放在第一个中间件。
在第一个中间件中使用 WriteAsync 方法写入body 会触发客户端响应,导致 header 被锁定
当第一次向 Response.Body 写入数据时:
- 服务器会发送响应头
- 此时 Response.HasStarted = true
- 之后再修改 Header 会抛异常
此时因为 header 已经被锁定,就无法在第二个中间件中再设置 header
如果想在第二个中间件中使用context.Response.ContentType = “text/html;charset=utf-8”; 那么需要注意不要在该中间件之前中间件不要触发客户端响应。
所以平时就尽量先设头,后写体。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)