21:中间件:掌控请求处理过程的关键(上)
.NET Core开发实战前文传送门:
- 第1课:课程介绍
- 第2课:内容综述
- 第3课:.NET Core的现状、未来以及环境搭建
- 第4课:Startup:掌握ASP.NET Core的启动过程
- 第5课:依赖注入:良好架构的起点(上)
- 第5课:依赖注入:良好架构的起点(中)
- 第5课:依赖注入:良好架构的起点(下)
- 第6课:作用域与对象释放行为(上)
- 第6课:作用域与对象释放行为(下)
- 第7课:用Autofac增强容器能力(上)
- 第7课:用Autofac增强容器能力(下)
- 第8课:配置框架:让服务无缝适应各种环境
- 第9课:命令行配置提供程序
- 第10课:环境变量配置提供程序
- 第11课:文件配置提供程序
- 第12课:配置变更监听
- 第13课:配置绑定:使用强类型对象承载配置数据
- 第14课:自定义配置数据源:低成本实现定制化配置方案
- 第15课:选项框架:服务组件集成配置的最佳实践
- 第16课:选项数据热更新:让服务感知配置的变化
- 第17课:为选项数据添加验证:避免错误配置的应用接收用户流量
- 第18课:日志框架:聊聊记日志的最佳姿势(上)
- 第18课:日志框架:聊聊记日志的最佳姿势(下)
- 第19课:日志作用域:解决不同请求之间的日志干扰
- 第20课 | 结构化日志组件Serilog:记录对查询分析友好的日志
21 | 中间件:掌控请求处理过程的关键
这一节讲解一下如何通过中间件来管理请求处理过程
中间件工作原理

next 表示后面有一个委托,每一层每一层套下去可以在任意的中间件来决定在后面的中间件之前执行什么,或者说在所有中间件执行完之后执行什么
整个中间件的处理过程实际上有两个核心对象:
IApplicationBuilder
RequestDelegate:处理整个请求的委托
源码链接:
https://github.com/witskeeper/geektime/tree/master/samples/MiddlewareDemo
IApplicationBuilder
namespace Microsoft.AspNetCore.Builder { public interface IApplicationBuilder { IServiceProvider ApplicationServices { get; set; } IDictionary<string, object> Properties { get; } IFeatureCollection ServerFeatures { get; } // 最终它会 Build 返回一个委托 // 这个委托就是把所有的中间件串起来之后,合并成一个委托方法 // 这个方法的入参可以看下方委托的定义 RequestDelegate Build(); IApplicationBuilder New(); // 它可以让我们去注册我们的中间件,把委托注册进去,每一个委托的入参也是一个委托 // 这也就意味着可以把这些委托注册成一个链,就像上面的图显示的那样 IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware); } }
委托的定义
namespace Microsoft.AspNetCore.Http { // 委托的入参是 HttpContext,所有的注册中间件的委托实际上都是对 HttpContext 的处理 public delegate Task RequestDelegate(HttpContext context); }
接着让我们看一下应用程序里面是怎么让它工作的?
之前课程讲过 Configure 方法是用来注册中间件的
app.UseMyMiddleware(); app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
根据刚才流程图表示的话,实际上中间件的执行顺序是跟注册顺序有关系的,最早注册的中间件它的权力是最大的,它可以越早的发生作用
中间件的注册实际上不仅仅是有上面展示的已有内置的中间件,实际上还可以用注册委托的方法来注册我们的逻辑
app.Use(async (context, next) => { await context.Response.WriteAsync("Hello"); });
因为这个中间件注册最早,而且不对后续的 next 做任何操作,所以启动之后无论输入什么都会输出 Hello
如果需要后续的中间件执行,那就意味着需要调用 next,可以在中间件执行之后再次 Hello 一次
app.Use(async (context, next) => { await context.Response.WriteAsync("Hello"); await next(); await context.Response.WriteAsync("Hello2"); });
启动程序报错:
System.InvalidOperationException: Headers are read-only, response has already started.
意味着一旦应用程序已经对 Response 输出内容,我们就不能对 header 进行操作了,但是可以在 Response 后续继续写出信息
app.Use(async (context, next) => { //await context.Response.WriteAsync("Hello"); await next(); await context.Response.WriteAsync("Hello2"); });
实际上除了 Use 这种方式的话,还有 Map 的方式
app.Map("/abc", abcBuilder => { abcBuilder.Use(async (context, next) => { //await context.Response.WriteAsync("Hello"); await next(); await context.Response.WriteAsync("Hello2"); }); });
启动程序不会直接看到 Hello 输出,如果把地址改为 localhost:5001/abc,我们的输出就会变成 Hello2
也就是说当我们需要对特定的路径进行指定中间件的时候可以这样做

原文出处:微信公众号【郑子铭 DotNet NB】
原文链接:https://mp.weixin.qq.com/s/Ke9CmwSOXMXzM25Iv1dIgA
本文观点不代表Dotnet9立场,转载请联系原作者。