asp.net core mvc 管道之中间件
asp.net core mvc 管道之中间件
- http请求处理管道通过注册中间件来实现各种功能,松耦合并且很灵活
- 此文简单介绍asp.net core mvc中间件的注册以及运行过程
- 通过理解中间件,将asp.net core mvc分解,以便更好地学习
中间件写法
- 先看一个简单的中间件,next是下一个委托方法,在本中间件的Invoke方法里面需要执行它,否则处理就会终止,消息处理到此中间件就会返回了
- 因此,根据这个约定,一个中间生成一个委托方法,需要把所有的委托方法处理成嵌套的委托,即每个中间件里面执行下一个委托,这样处理过程就像管道一样连接起来,每个中间件就是管道处理的节点
- 至于为什么要这样写中间件,这是约定好的,还有注意点,下面将会讲到
public class Middleware { private readonly RequestDelegate _next; public RouterMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext httpContext) { // do something await _next.Invoke(httpContext); // do something } }
中间件管道生成
- 以上中间件会通过方法生成一个委托,并添加到委托集合,中间生成委托的过程后面讲
namespace Microsoft.AspNetCore.Builder.Internal { public class ApplicationBuilder : IApplicationBuilder { private readonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>(); public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware) { _components.Add(middleware); return this; } } }
- 最后的
ApplicationBuilder.Build
方法会处理所有注册的中间件生成的委托集合,将所有中间件生成的委托集合,处理成嵌套的形式,最终得到一个委托,连成一段管道。 - 以下方法首先声明一个响应404的委托方法,把它当成所有中间件的最后一个,当然它不一定会被执行到,因为某个中间件可能不会调用它
- 然后将这个委托作为参数,传入并执行_components这个委托集合里面的每一个委托,_components就是所有注册的中间件生成的委托集合,
Reverse
方法将集合反转,从最后注册的中间件对应的委托开始处理 - 所以呢中间件的注册是有顺序的,也就是
Startup.cs
类里面的Configure
方法,里面的每个Use开头的方法都对应一个中间件注册,代码的顺序就是注册的顺序,也是执行的顺序,千万不能写错了。因为MVC处于处理流程的最后面,因此UseMvc方法总是位于最后 - 在看
component
,是从_components
委托集合里面取出来的,执行后又得到一个RequestDelegate
类型的委托,因此由中间件生成的委托的类型应该是Func<RequestDelegate, RequestDelegate>
public RequestDelegate Build() { RequestDelegate app = context => { context.Response.StatusCode = 404; return Task.CompletedTask; }; foreach (var component in _components.Reverse()) { app = component(app); } return app; }
中间件生成委托
- 以下是中间件注册方法,实际是调用
ApplicationBuilder.Use
方法,将中间件生成的委托加入委托集合,完成中间件注册 -
app.Use
方法参数,就是上面需要的类型Func<RequestDelegate, RequestDelegate>
的委托,该委托的参数next
就是下一个中间件对应的委托,返回值就是中间件的Invoke
方法对应的委托,该方法用到了next
- 源码位于Microsoft.AspNetCore.Builder.UseMiddlewareExtensions这个类
public static IApplicationBuilder UseMiddleware<TMiddleware>(this IApplicationBuilder app, params object[] args) { return app.UseMiddleware(typeof(TMiddleware), args); } public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args) { // 省略部分代码 var applicationServices = app.ApplicationServices; return app.Use(next => { // 省略部分代码 var ctorArgs = new object[args.Length + 1]; ctorArgs[0] = next; Array.Copy(args, 0, ctorArgs, 1, args.Length); var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs); if (parameters.Length == 1) { return (RequestDelegate)methodinfo.CreateDelegate(typeof(RequestDelegate), instance); } var factory = Compile<object>(methodinfo, parameters); return context => { var serviceProvider = context.RequestServices ?? applicationServices; if (serviceProvider == null) { throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider))); } return factory(instance, context, serviceProvider); }; }); }
中间件写法约定
- 看以上代码,第一种写法,首先如果中间继承自
IMiddleware
接口,则调用UseMiddlewareInterface
方法。使用了接口规范,那么你也不能乱写了,只需要注意在Invoke
方法调用next
即可
private static IApplicationBuilder UseMiddlewareInterface(IApplicationBuilder app, Type middlewareType) { return app.Use(next => { return async context => { var middlewareFactory = (IMiddlewareFactory)context.RequestServices.GetService(typeof(IMiddlewareFactory)); if (middlewareFactory == null) { // No middleware factory throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoMiddlewareFactory(typeof(IMiddlewareFactory))); } var middleware = middlewareFactory.Create(middlewareType); if (middleware == null) { // The factory returned null, it's a broken implementation throw new InvalidOperationException(Resources.FormatException_UseMiddlewareUnableToCreateMiddleware(middlewareFactory.GetType(), middlewareType)); } try { await middleware.InvokeAsync(context, next); } finally { middlewareFactory.Release(middleware); } }; }); }
- 第二种是开头举的例子,不继承自接口
- 至少要有名为
Invoke
或InvokeAsync
的一个方法
public static class UseMiddlewareExtensions { internal const string InvokeMethodName = "Invoke"; internal const string InvokeAsyncMethodName = "InvokeAsync"; } var invokeMethods = methods.Where(m => string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal) || string.Equals(m.Name, InvokeAsyncMethodName, StringComparison.Ordinal) ).ToArray();
- 类的方法只能有一个
- 至少要有名为
if (invokeMethods.Length > 1) { throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName, InvokeAsyncMethodName)); } if (invokeMethods.Length == 0) { throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName, InvokeAsyncMethodName, middleware)); }
- 类的返回值是
Task
var methodinfo = invokeMethods[0]; if (!typeof(Task).IsAssignableFrom(methodinfo.ReturnType)) { throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, InvokeAsyncMethodName, nameof(Task))); }
- 方法的参数至少有一个,且第一个参数必须为是
HttpContext
var parameters = methodinfo.GetParameters(); if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext)) { throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName, nameof(HttpContext))); }
- 方法的参数如果只有一个,则将
UseMiddleware
方法传入的自定义参数args
加上下一个委托next
,得到新的参数数组,然后创建中间件实例,生成Invoke
方法对应委托。此处注意,如果中间件的构造函数中有其它参数,但是未注册到ApplicationServices
的话,需要在UseMiddleware
方法中传入
var ctorArgs = new object[args.Length + 1]; ctorArgs[0] = next; Array.Copy(args, 0, ctorArgs, 1, args.Length); var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs); if (parameters.Length == 1) { return (RequestDelegate)methodinfo.CreateDelegate(typeof(RequestDelegate), instance); }
- 方法的参数如果多于一个,则调用
Compile
方法,生成一个委托,该委托从IServiceProvider
中获取需要的参数的实例,再调用Invoke
方法,相比上面的情况,多了一步从IServiceProvider
获取实例,注入到Invoke
而已。 -
Compile
方法使用了Linq表达式树,源码位于Microsoft.AspNetCore.Builder.UseMiddlewareExtensions,此处不作讲解,因为我也不太懂
var factory = Compile<object>(methodinfo, parameters); return context => { var serviceProvider = context.RequestServices ?? applicationServices; if (serviceProvider == null) { throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider))); } return factory(instance, context, serviceProvider); };
总结
- 以上就是通过调试和阅读源码分析得到的结果,写出来之后阅读可能有偏差,但这是为了方便大家理解,感觉这个顺序介绍会好理解点,反正我是理解了,介绍顺序对我影响不大
- 通过动手记录的过程,把之前调试阅读的时候没发现或者没理解的点都找到弄明白了,整明白了中间件的注册过程以及需要注意的书写规范,收获显而易见,所以源码才是最好的文档,而且文档未必有这么详细。通过记录,可以把细节补全甚至弄明白,这一点至关重要,再次体会到其重要性
- 另外,千万不要在大晚上写技术博文啊,总结之类的东西,切记
最后,文章可能有更新,请阅读原文获得更好的体验哦 https://www.cnblogs.com/xxred/p/9576622.html
用心做好每一件事,结果会给你最大的惊喜!低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
在.NET Core中三种实现“可插拔”AOP编程方式(附源码)
一看标题肯定会联想到使用动态编织的方式实现AOP编程,不过这不是作者本文讨论的重点。 本文讨论另外三种在netcore中可实现的方式,Filter(过滤器,严格意义上它算是AOP方式),DynamicProxy(动态代理方式,JAVA上早已不是新鲜事),Middleware(netcore中间件所实现的AOP方式) 什么是AOP编程 在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。 换白话文就是:保证开闭原则的前提下,不修改某一个模块(或函数)的任何一行代码,从而实现对该模块的横向扩展。 可插拔:即使抛弃AOP,核心内容仍然可以运行,降低耦合,提高可重用。 创建一个“能说你好,某某某”的ASP .NET CORE WebApi项目 创...
- 下一篇
飞天技术汇 | 阿里云Redis产品升级大全
阿里云Redis重磅产品升级:全球多活版、混合存储版、多线程性能增强版。 这期飞天技术汇你将看到 ● 企业如何实现业务快速全球化布局 ● 冷热数据如何分离 ● 多IO线程如何面对高并发业务 助力企业全球化业务部署 Redis全球多活产品是阿里自研的、基于云数据库 Redis 版(ApsaraDB for Redis)、100%兼容 Redis 协议的多活数据库系统。通过数据同步通道,把多个Redis实例组网成1个逻辑上的 Redis 多活实例,多活实例内的所有实例均可读写并保持实时数据同步。 数据同步通道通过内网打通,具有高可靠、高安全,低延迟的特性。阿里云Redis多活架构可以帮助企业快速进行全球化业务部署。 阿里云Redis全球多活的优势 ⊙地域分布广:阿里云18个全球region任意部署 ⊙配置灵活:可以任意增加,删除多活子节点 ⊙低延迟:通过内
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2整合Redis,开启缓存,提高访问速度
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Hadoop3单机部署,实现最简伪集群
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- CentOS7安装Docker,走上虚拟化容器引擎之路
- CentOS8编译安装MySQL8.0.19
- Docker安装Oracle12C,快速搭建Oracle学习环境