换个角度学习ASP.NET Core中间件
换个角度学习ASP.NET Core中间件
中间件真面目
关于ASP.NET Core中间件是啥,简单一句话描述就是:用来处理HTTP请求和响应的一段逻辑,并且可以决定是否把请求传递到管道中的下一个中间件!
上面只是概念上的一种文字描述,那问题来了,中间件在程序中到底是个啥❓
一切还是从IApplicationBuilder说起,没错,就是大家熟悉的Startup类里面那个Configure方法里面的那个IApplicationBuilder(有点绕😵,抓住重点就行)。
IApplicationBuilder,应用构建者,听这个名字就能感受它的核心地位,ASP.NET Core应用就是依赖它构建出来,看看它的定义:
public interface IApplicationBuilder
{
//...省略部分代码... IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware); RequestDelegate Build();
}
Use方法用来把中间件添加到应用管道中,此时我们已经看到中间件的真面目了,原来是一个委托,输入参数是RequestDelegate,返回也是RequestDelegate,其实RequestDelegate还是个委托,如下:
public delegate Task RequestDelegate(HttpContext context);
还记得中间件是干嘛的吗?是用来处理http请求和响应的,即对HttpContext的处理,这里我们可以看出来原来中间件的业务逻辑就是封装在RequestDelegate里面。
总结一下:
middleware就是Func,输入的是下一个中间件的业务处理逻辑,返回的就是当前中间件的业务处理逻辑,并在其中决定要不要调用下个中间件!我们代码实现一个中间件看看(可能和我们平时用的不太一样,但它就是中间件最原始的形式!):
//Startup.Configure方法中
Func middleware1 = next => async (context) =>
{ //处理http请求 Console.WriteLine("do something before invoke next middleware in middleware1"); //调用下一个中间件逻辑,当然根据业务实际情况,也可以不调用,那此时中间件管道调用到此就结束来了! await next.Invoke(context); Console.WriteLine("do something after invoke next middleware in middleware1"); };
//添加到应用中
app.Use(middleware1);
跑一下瞅瞅,成功执行中间件!
IIS Express is running.
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: E:\vs2019Project\WebApplication3\WebApplication3
do something before invoke next middleware in middleware1
do something after invoke next middleware in middleware1
中间件管道
通过上面我们有没有注意到,添加中间时,他们都是一个一个独立的被添加进去,而中间件管道就是负责把中间件串联起来,实现下面的一个中间件调用流转流程:
如何实现呢?这个就是IApplicationBuilder中的Build的职责了,再次看下定义:
public interface IApplicationBuilder
{
//...省略部分代码...
IApplicationBuilder Use(Func middleware);
RequestDelegate Build();
}
Build方法一顿操作猛如虎,主要干一件事把中间件串联起来,最后返回了一个 RequestDelegate,而这个就是我们添加的第一个中间件返回的RequestDelegate,
看下框架默认实现:
//ApplicationBuilder.cs
public RequestDelegate Build()
{ RequestDelegate app = context => { // If we reach the end of the pipeline, but we have an endpoint, then something unexpected has happened. // This could happen if user code sets an endpoint, but they forgot to add the UseEndpoint middleware. var endpoint = context.GetEndpoint(); var endpointRequestDelegate = endpoint?.RequestDelegate; if (endpointRequestDelegate != null) { var message = $"The request reached the end of the pipeline without executing the endpoint: '{endpoint.DisplayName}'. " + $"Please register the EndpointMiddleware using '{nameof(IApplicationBuilder)}.UseEndpoints(...)' if using " + $"routing."; throw new InvalidOperationException(message); } context.Response.StatusCode = 404; return Task.CompletedTask; }; foreach (var component in _components.Reverse()) { app = component(app); } return app; }
Build方法里面定义了一个 RequestDelegate ,作为最后一个处理逻辑,例如返回404。
_components存储着添加的所有中间件
中间件管道调度顺序,就是按照中间添加的顺序调用,所以中间件的顺序很重要,很重要,很重要!
遍历_components,传入next RequestDelegate,获取当前RequestDelegate,完成管道构建!
中间件使用
在此之前,还是提醒下,中间件最原始的使用姿势就是
IApplicationBuilder Use(Func middleware);
下面使用的方式,都是对此方式的扩展!
Lamda方式
大多数教程里面都提到的方式,直接上代码:
//扩展方法
//IApplicationBuilder Use(this IApplicationBuilder app, Func, Task> middleware)
app.Use(async (context, next) =>
{ Console.WriteLine("in m1"); await next.Invoke(); Console.WriteLine("out m1"); });
扩展方法简化了中间件的使用,这个里面就只负责写核心逻辑,然后扩展方法中把它包装成Func类型进行添加,不像原始写的那样复杂,我们看下这个扩展方法实现,哈,原来就是一个简单封装!我们只要专注在middleware里面写核心业务逻辑即可。
public static IApplicationBuilder Use(this IApplicationBuilder app, Func, Task> middleware)
{ return app.Use(next => { return context => { Func<Task> simpleNext = () => next(context); return middleware(context, simpleNext); }; }); }
如果我们定义中间件作为终端中间件(管道流转此中间件就结束了,不再调用后面的中间件)使用时,上面只要不调用next即可。
当然我们还有另外一个选择,自己使用扩展Run方法,传入的参数就是RequestDelegate,还是上代码:
//扩展方法
//public static void Run(this IApplicationBuilder app, RequestDelegate handler);
app.Run(async (context) =>
{ Console.WriteLine("in m3"); await context.Response.WriteAsync("test22"); Console.WriteLine("out m3"); });
到此,我们有没有发现上面的方式有些弊端,只能处理下简单逻辑,如果要依赖第三方服务,那可怎么办?
定义中间件类方式
使用中间件类,我们只要按照约定的方式,即类中包含InvokeAsync方法,就可以了。
使用类后,我们就可以注入我们需要的第三方服务,然后完成更复杂的业务逻辑,上代码
//定义第三方服务
public interface ITestService
{ Task Test(HttpContext context); } public class TestService : ITestService { private int _times = 0; public Task Test(HttpContext context) { return context.Response.WriteAsync($"{nameof(TestService)}.{nameof(TestService.Test)} is called {++_times} times\n"); } }
//添加到IOC容器
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ITestService, TestService>(); }
//中间件类,注入ITestService
public class CustomeMiddleware1
{ private int _cnt; private RequestDelegate _next; private ITestService _testService; public CustomeMiddleware1(RequestDelegate next, ITestService testService) { _next = next; _cnt = 0; _testService = testService; } public async Task InvokeAsync(HttpContext context) { await _testService?.Test(context); await context.Response.WriteAsync($"{nameof(CustomeMiddleware1)} invoked {++_cnt} times"); } }
//添加中间件,还是一个扩展方法,预知详情,请看源码
app.UseMiddleware();
运行一下,跑出来的结果如下,完美!
等一下,有没有发现上面有啥问题???❓
明明ITestService是以Transient注册到容器里面,应该每次使用都是新实例化的,那不应该被显示被调用 15 次啊!!!
这个时候我们应该发现,我们上面的所有方式添加的中间件的生命周期其实和应用程序是一致的,也就是说是只在程序启动的时候实例化一次!所以这里第三方的服务,然后以Transient方式注册到容器,但在中间件里面变现出来就是一个单例效果,这就为什么我们不建议在中间件里面注入DbContext了,因为DbContext我们一般是以Scoped来用的,一次http请求结束,我们就要释放它!
如果我们就是要在中间件里面是有ITestService,而且还是Transient的效果,怎么办?
实现IMiddleware接口
//接口定义
public interface IMiddleware
{
ask InvokeAsync(HttpContext context, RequestDelegate next);
}
//实现接口
public class CustomeMiddleware : IMiddleware
{ private int _cnt; private ITestService _testService; public CustomeMiddleware(ITestService testService) { _cnt = 0; _testService = testService; } public async Task InvokeAsync(HttpContext context, RequestDelegate next) { await _testService?.Test(context); await context.Response.WriteAsync($"{nameof(CustomeMiddleware)} invoked {++_cnt} times"); } }
//添加中间件
app.UseMiddleware();
运行一下,结果报错了... ,提示CustomeMiddleware没有注册!
InvalidOperationException: No service for type 'WebApplication3.CustomeMiddleware' has been registered.
通过报错信息,我们已经知道,如果实现了IMiddleware接口的中间件,他们并不是在应用启动时就实例化好的,而是每次都是从IOC容器中获取的,其中就是IMiddlewareFactory
来解析出对应类型的中间件的(内部就是调用IServiceProvider),了解到此,我们就知道,此类中间件此时是需要以service的方式注册到IOC容器里面的,这样中间件就可以根据注册时候指定的生命周期方式来实例化,从而解决了我们上一节提出的疑问了!好了,我们注册下中间件服务
public void ConfigureServices(IServiceCollection services)
{ services.AddTransient<CustomeMiddleware>(); services.AddTransient<ITestService, TestService>(); }
再次多次刷新请求,返回都是下面的内容
TestService.Test is called 1 times
CustomeMiddleware invoked 1 times
结语
中间件存在这么多的使用方式,每一个存在都是为了解决实际需求的,当我们了解这些背景知识后,在后面自己使用时,就能更加的灵活!
作者:小伟06

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
阿里云建网站的几种方式
使用阿里云建网站的三种方式购买云服务器手动建站、云速成美站模板建站或者选择阿里云定制建站三种方式,站长分享利用阿里云创建网站的三种方式及优势对比: 阿里云建站方法汇总 使用阿里云建站可以有三种方式,一种是购买ECS云服务器,然后自行手动搭建网站,需要技术门槛;第二种方式是购买阿里云官网云速成美站,使用模板建站,阿里云提供上千套模板,模板建站价格便宜,会打字就会建站;第三种是使用阿里云官方定制建站,需要什么样的网站什么功能,阿里云建站专家提供一对一网站定制。参考下表: 阿里云建站方式 所需产品 优势 适用人群 自助建站 ECS云服务器 自行购买云服务器,手动搭建网站 需要些技术门槛,适用于刚接触云计算或对云服务器和建站不太了解、希望自行设计网站的个人或小企业用户。 模板建站 云·速成美站 使用阿里云提供上千套模板,可视化后台管理,会打字就会建站 适合有一定软件应用能力的个人或小企业用户,模板建站支持Web站点、移动端站点、互动表单以及会员支付多场景。 定制建站 云·企业网站定制和功能定制 由阿里云专业网站设计师完成网站设计及搭建 适合对网站有品质要求或个性化需求、希望节省人力和时间成本的...
- 下一篇
物联网和智慧城市现状
云栖号资讯:【点击查看更多行业资讯】在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来! 智慧城市项目正在实现并进入主流意识。以下是有关使智慧城市成功的十个观察。 随着智慧城市走出炒作阶段,开始扩散并向前发展,其数量已达到临界点。 过去几年,全球宣布了数十个智慧城市项目。早期采用者已经在实施他们的智慧城市计划。 在智慧城市项目在全球范围内受到关注并且早期采用者已开始兑现其智慧城市愿景的时候,我们认为现在应该就智慧城市空间的运行状况进行一些高层次的观察。 以下是我们的十大观察结果: 1. 政策制定者对智慧城市计划越来越重视 这听起来似乎很明显,但并非总是如此。决策者现在已经更好地理解了智慧城市项目可以为城市带来的潜在好处,例如增加的经济机会,减少的交通拥堵,更少的污染和更高的宜居性。 尽管智慧城市计划的投资案例仍然具有挑战性,但这种加深的理解意味着决策者对将此类计划出售给居民和其他利益相关者更有信心。 2. 智慧城市投资案例不同 与部署物联网的其他行业相比,智慧城市的公共投资案例要复杂得多。 这源于预算限制公共支出,以及难以就支出优先事项达成政治共识。与智慧城市相关的许多好处也...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- CentOS7安装Docker,走上虚拟化容器引擎之路
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- CentOS8安装Docker,最新的服务器搭配容器使用
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- Red5直播服务器,属于Java语言的直播服务器
- CentOS6,CentOS7官方镜像安装Oracle11G