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 somethingawait _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 factorythrow new InvalidOperationException(Resources.FormatException_UseMiddlewareNoMiddlewareFactory(typeof(IMiddlewareFactory)));}var middleware = middlewareFactory.Create(middlewareType);if (middleware == null){// The factory returned null, it's a broken implementationthrow new InvalidOperationException(Resources.FormatException_UseMiddlewareUnableToCreateMiddleware(middlewareFactory.GetType(), middlewareType));}try{await middleware.InvokeAsync(context, next);}finally{middlewareFactory.Release(middleware);}};});}
  • 第二种是开头举的例子,不继承自接口

    • 至少要有名为InvokeInvokeAsync的一个方法
      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

转载于:https://www.cnblogs.com/xxred/p/9576622.html

asp.net core mvc 管道之中间件相关推荐

  1. ASP.NET Core MVC 从入门到精通之路由

    随着技术的发展,ASP.NET Core MVC也推出了好长时间,经过不断的版本更新迭代,已经越来越完善,本系列文章主要讲解ASP.NET Core MVC开发B/S系统过程中所涉及到的相关内容,适用 ...

  2. [ASP.NET Core MVC] 如何实现运行时动态定义Controller类型?

    昨天有个朋友在微信上问我一个问题:他希望通过动态脚本的形式实现对ASP.NET Core MVC应用的扩展,比如在程序运行过程中上传一段C#脚本将其中定义的Controller类型注册到应用中,问我是 ...

  3. 通过极简模拟框架让你了解ASP.NET Core MVC框架的设计与实现[上篇]

    <200行代码,7个对象--让你了解ASP.NET Core框架的本质>让很多读者对ASP.NET Core管道有了真实的了解.在过去很长一段时间中,有很多人私信给我:能否按照相同的方式分 ...

  4. 《从零开始学ASP.NET CORE MVC》课程介绍

    大家好,欢迎来到52ABP学院,收看我们的 <从零开始学ASP.NET CORE MVC>. ASP.NET Core 简介 从2015年开始随时互联网成长,云计算和AI.大数据的爆发,大 ...

  5. ASP.NET Core MVC 源码学习:MVC 启动流程详解

    前言 在 上一篇 文章中,我们学习了 ASP.NET Core MVC 的路由模块,那么在本篇文章中,主要是对 ASP.NET Core MVC 启动流程的一个学习. ASP.NET Core 是新一 ...

  6. ASP.NET Core MVC 源码学习:Routing 路由

    前言 最近打算抽时间看一下 ASP.NET Core MVC 的源码,特此把自己学习到的内容记录下来,也算是做个笔记吧. 路由作为 MVC 的基本部分,所以在学习 MVC 的其他源码之前还是先学习一下 ...

  7. 用 Visual Studio 和 ASP.NET Core MVC 创建首个 Web API

    原文:Building Your First Web API with ASP.NET Core MVC and Visual Studio 作者:Mike Wasson 和 Rick Anderso ...

  8. ASP.NET Core MVC中的两种404错误

    我们在使用某些系统的时候,如果访问了一个不存在的地址怎么办,比如http://52abp. com/airport/fly:或者访问的地址路由和操作方法虽然存在,但是地址参数id不存在,比如https ...

  9. ASP.NET Core MVC 项目 Autofac

    目录 一:基本使用 二:简单理解Autofac容器 三:多种注册方式 四:构造函数注入 一:默认构造函数注入 二:选择一个构造函数参数的构造函数 五:属性注入 一:属性注入 二:属性注入扩展--指定属 ...

最新文章

  1. 关于Python爬虫原理和数据抓取1.1
  2. python可变交换性能优化
  3. 个人支付宝账号也可以做付款后自动发货发卡系统,附:源码+demo
  4. SDN,这一年都经历了什么
  5. 基于matlab的卷积码实验报告,基于MATLAB的卷积码编译码设计仿真.doc
  6. 长春理工大学第十四届程序设计竞赛(重现赛)B
  7. iOS底层探索之多线程(九)—GCD源码分析(栅栏函数)
  8. linux ssh 免密登陆
  9. oracle分区详解
  10. 什么是强制性3C认证?
  11. 数字集成电路物理设计_数字世界的物理词汇
  12. React中文文档 9. 表单
  13. java天气预报免费接口api_免费天气API,可以获取全国范围五天内的天气预报(含完整Demo)...
  14. 刀口法测mtf_基于特征像分析的光学镜头MTF测试方法的研究
  15. 前TT(前T/T)与后TT(后T/T),以及信用证(LC,L/C)付款方式比较
  16. 车牌识别关键技术-车牌定位
  17. Windows Sever 2016 创建DNS服务器并配置转发器及条件转发器
  18. CVE-2017-7494紧急预警:Samba蠕虫级提权漏洞,攻击代码已在网上扩散
  19. VS2022 Visual Studio 2022专业版全功能离线版下载
  20. 中国版权保护中心注册不了的解决办法

热门文章

  1. 第一行代码 Android (郭霖 著)
  2. springboot+aop切点记录请求和响应信息
  3. 【Oracle学习笔记】常用知识梳理
  4. Boostrap日期时间选择器
  5. bat管理mstsc 远程桌面连接
  6. ​求1000以内是3或者是5的倍数的值的和
  7. 怎么使用PHPMailer实现邮件的发送??
  8. 为什么要重写hashCode()
  9. idea 拉取gitee代码_手把手撸一个 IDEA 插件
  10. python获取返回值_【Python】python获取线程的返回值