关于 ASP.NET Core 中的管道和中间件
本文章介绍在 ASP.NET Core 中的管道和中间件
先决条件
介绍
ASP.NET Core 应用本身就是一个长时间运行的托管服务,这个托管服务的名称是 GenericWebHostService,ASP.NET Core 的很多特性都是由来实现的,路由,会话,缓存,认证授权,甚至可以通过管道定制创建属于自己的 Web 框架
管道是 ASP.NET Core 应用的核心,任何一个 ASP.NET Core 的框架都离不开 管道
ASP.NET Core 和 ASP.NET 的管道一样都是 责任链模式,不同的是 ASP.NET Core 的管道由开发者自己组装,而 ASP.NET 的管道已经在框架中硬编码,开发者只能对责任链上的事件进行编码
一个简单的 WebHost
在 ASP.NET Core 的默认模板中,HostBuilder 的初始化是通过 Host.CreateDefaultBuilder 方法初始化,然后通过 IHostBuilder 的 ConfigureWebHostDefaults 初始化 WebHost 的服务
其中 Host.CreateDefaultBuilder 主要做得事情是加载环境变量配置,加载默认的配置文件配置,配置日志组件等等
而 ConfigureWebHostDefaults 主要做得就是注册一个名为 GenericWebHostService 的托管服务,然后对这个托管服务进行相关配置
在上面的代码中,UseKestrel 指定 Kestrel 作为 WebHost 要使用的服务器,要使用 IIS 需要 UseIIS
然后在管道中加了一个返回 Hello world 的中间件
当 WebHost 启动以后,构建的处理管道才会被真正构建出来,这个管道被绑定到 Kestrel 默认的端口并开始监听请求
当 Http 请求一旦到达,这个服务器就会将其标准化为一个 HttpContext 对象,然后发送给管道
而实际处理请求的是配置在管道中的各个组件,这个组件我们称为管道中间件,每个组件都有各自的功能
比如专门实现路由的中间件,还有实现用户认证和授权的中间件,
所谓的配置管道就是具体的需求,选择对应的中间件来构建最终的管道
而组成请求管道的中间件是一个委托
请求处理管道其实是由一个服务器加一组中间件所构成的
服务器负责请求的监听、接收、分发和最终的响应
中间件的注册与执行顺序
中间件的注册可以通过 Configure 中的 IApplicationBuilder 进行注册,通过 Use 方法直接将 MiddleWare 添加到当前的管道中,管道可以理解为委托组成的一条责任链,Use 方法接受的参数是 Func<RequestDelegate, RequestDelegate>,入参 next 表示管道中的下一个中间件,如果不在请求管道中调用 next 方法,即管道短路不再将请求继续往下传递,而是结束请求开始响应。管道短路可以避免不必要的工作
按照上面示例我们可以看到浏览器输出的内容就是我们中间件注册的顺序来依次处理响应
中间件的定义
我们可以使用委托 Lamaba 表达式 来定义中间件, 但是我们在大部分情况下,我们可以将自定义中间件定义为一个具体的类型
ASP.NET Core 提供了两种使用具体类型定义中间件的方法。一种是基于接口实现的中间件,一种是基于约定的中间件
基于接口实现
强类型中间件需要实现 IMiddleware 接口,该接口定义了 InvokeAsync 的方法,请求的处理代码就是写在该方法中,该方法第一个参数是 HttpContext,第二个参数代表着后续中间件组成的管道委托
如果 HttpContext 还需要后续中间件进行处理的话,只需要调用 next 委托就可以了,如果不调用就会发生管道短路
由于中间件是采用依赖注入的方式来提供的,所以我们还要预先进行服务注册,然后才能通过 IApplicationBuilder.UseMiddleware 方法注册中间件
基于约定定义
基于约定的中间件需要有一个有效的公共构造函数,这个函数必须包含 RequestDelegate 类型(建议第一个参数写 RequestDelegate 类型),通过依赖注入方式提供的和自定义的参数可以混编
里面必须要 InvokeAsync 或者 Invoke 的方法,该方法接收类型为 HttpContext 的一个参数
自定义的参数通过 UseMiddleware 方法传入,由于依赖注入方式提供的参数和自定义的参数可以混编,自定义的参数在这里也可以随意摆放,但是相同的类型比如自定义了两个 bool 类型的参数,在这里将会是第一个传入的 bool 类型的值将会赋值给在构造函数中遇到的第一个 bool 类型参数
基于约定的中间件的中间件不用手动注册,框架会自动以单例生命周期进行服务注册,还可以给中间件传参
由于基于约定的中间件的生命周期被自动注册为单例模式,我们不应该在他的构造函数中去注入生命周期为作用域或者瞬时的服务,因为永远不会被释放掉
注册服务和中间件
在上文第三节基于接口实现中,使用了 IWebHostBuilder.ConfigureServices 方法注册服务,用 IWebHostBuilder.Configure 方法注册中间件,还可以使用约定的形式,用 IWebHostBuilder.UseStartup 来注册服务和注册中间件
服务注册方法 Startup.ConfigureServices 并不是在任何情况下都需要,可能应用不依赖于任何服务,该方法为可选
由于 Startup.ConfigureServices 方法调用是在整个服务注册的最后阶段,可以通过 IServiceCollection 去获取之前注册的所有服务
在 IHostBuilder.ConfigureServices 和 IWebHostBuilder.ConfigureServices 中注册的服务,都无法在 Startup 的构造函数中注入,因为这时这些服务还没注册到 IServiceCollection(服务注册对象)中,只能在 Startup 中注入框架的公共服务
而 Startup.Configure 方法调用的时间比较晚,所以任意方式注册的服务都可以注册到这个方法中
中间件中也可以注册服务,由于 ASP.NET Core 在创建中间件对象并利用它们构建整个请求处理管道的时候,所有的服务已经注册完成了,所以任何一个注册的服务都可以注册到中间件构造函数中
版权声明: 本文为 InfoQ 作者【雄鹿 @】的原创文章。
原文链接:【http://xie.infoq.cn/article/6f260331d4524ffcd91cb9a03】。
本文遵守【CC BY-NC】协议,转载请保留原文出处及本版权声明。
评论