.NET Core中间件的注册和管道的构建(2)---- 用UseMiddleware扩展方法注册中间件类

0x00 为什么要引入扩展方法

有的中间件功能比较简单,有的则比较复杂,并且依赖其它组件。除了直接用ApplicationBuilder的Use()方法注册中间件外,还可以使用ApplicationBuilder的扩展方法UseMiddleware()注册中间件。这种情况下可以注册类型,这个方法会通过反射解析这个类型,并把它包装成Func<ReuqestDelegate,RequestDelegate>然后调用Use()方法注册。

遇到这种情况一般直觉上是通过继承一个抽象类并实现其中的方法在写一个中间件。不过.NET Core不是这么做的。中间件类使用约定而不是继承来进行约束。这里说的约定就是约定原本的意思,例如约定好了中间件类中必须包含一个叫Invoke的方法,叫别的就不行,有重载也不行。因为中间件类没有任何继承上的约束,在注册过程中就是通过反射去寻找名字为Invoke的方法,然后把它包装成RequestDelegate的。这篇文章就是要说一下写一个中间件类都有哪些约定以及中间件类的注册。

0x01 一个最简单的例子

先看一个中间件类的最简单的例子:

上一篇文章中说过了,中间件本质就是一个方法,这个方法接收一个HttpContext参数,返回Task。在上面这个中间件类中Invoke就是这个方法。为了能够调用下一个中间件,当前中间件还需要保存下一个中间件的引用。这个引用是通过构造函数传进来的,如果当前中间件不需要调用后面中间件的话,这个引用完全可以不保存。如果要注册这个中间件,我们可以这样做:

但如果我们这个中间件比较复杂,依赖很多其他模块,那么我们在注册的时候需要构造依赖模块的实例,并在中间件类的构造函数中把这些依赖传进去。这加强了中间件和依赖模块之间的耦合度。为了能减少这种耦合,同时享受到依赖注入带来的便利,提供了UseMiddleware<T>扩展方法来注册中间件类T。

UseMiddleware扩展方法会找到上面中间件类中的Invoke方法,创建上面类的实例,在创建实例时遇到需要注入的类型会尝试注入,然后把Invoke方法包装为ReuqestDelegate,进而包装为Func<RequestDelegate,RequestDelegate>,然后通过ApplicatonBuilder的Use方法(上篇文章讲过了)注册到IList<Func<RequestDelegaet,RequestDelegate>中。

从上面的SimpleMiddleware我们可以看到这个类没有任何显示的继承关系,那么我们在写一个中间件类时需要注意哪些约束呢?我们只要看一下UseMiddleware注册中间件的过程就明白了。下面是对UseMiddleware()方法的分析,对代码分析不感兴趣的可以跳过直接看后面的结论和测试。

0x02 扩展方法注册中间件类的过程

使用UseMiddleware<T>扩展方法注册中间件类T主要包含以下几个关键步骤:

1.找到中间件类的Invoke方法。UseMiddleware方法会通过反射获取注册的中间件类的所有public且非static的方法列表,然后从其中找出名字叫Invoke的方法,确认Invoke方法没有重载,确认Invoke方法返回Task,确认Invoke方法第一个参数是HttpContext,最后这两个检查是为了能把Invoke方法包装为RequestDelegate。

2.选取最佳构造函数。把下一个中间件的引用next插入到从UseMiddleware传入的参数列表的第一个,作为给定的参数列表。

然后获取中间件类的所有构造函数,从给定的参数列表中依次取出参数,和构造函数的参数进行类型匹配,匹配最多的构造函数选为最佳构造函数。匹配相同的以代码中排在前面的构造函数为准(这其中省略了很多匹配最佳构造函数的细节,感兴趣的可以自行查看代码)。

值得注意的是如果存在给定的参数列表中存在某个参数P,在当前构造函数参数列表中找不到与之匹配的类型,那么这个构造函数不能作为最佳构造函数。也就是说选中的最佳构造函数的参数列表必须要是给定参数列表的超集。刚刚上面也说了,下一个中间件next被插入到了给定参数列表的第一个,因此选中的最佳构造函数参数中必须包含参数RequestDelegate。如果所有构造函数都不包含RequestDelegate,那么会抛出异常。

3.构造中间件类的实例。找到了最佳构造函数后,接下来就使用该构造函数构造中间件类的实例。对于构造函数中的所有参数,能够从给定的参数列表中找到类型匹配的,从给定的参数列表中获取参数。从参数列表中找不到的,则尝试从依赖注入容器中获取,依赖注入容器中也找不到的检查是不是有默认值,默认值也没有就抛出异常。

4.实例构造完成后,如果Invoke方法只有一个参数(HttpContext)会把这个实例的Invoke方法包装为RequestDelegate,进而包装为Func<RequestDelegate,RequestDelegate>然后使用Use方法注册。如果有多个参数,不符合RequestDelegate约束,则对Invoke进行二次包装以符合RequestDelegate。在二次包装中会尝试从依赖注入容器中获取Invoke参数中的依赖。

0x03一些结论

下面总结一下中间件类的一些约定,主要是基于对代码的理解,有错误或不全的地方请指正。

关于中间件的方法:

1.中间件的方法必须叫Invoke,且为public,非static。

2.Invoke方法第一个参数必须是HttpContext类型。

3.Invoke方法必须返回Task。

4.Invoke方法可以有多个参数,除HttpContext外其它参数会尝试从依赖注入容器中获取。

5.Invoke方法不能有重载。

关于构造函数:

1.构造函数必须包含RequestDelegate参数,该参数传入的是下一个中间件。

2.构造函数参数中的RequestDelegate参数不是必须放在第一个,可以是任意位置。

3.构造函数可以有多个参数,参数会优先从给定的参数列表中找,其次会从依赖注入容器中获取,获取失败会尝试获取默认值,都失败会抛出异常。

4.构造函数可以有多个,届时会根据构造函数参数列表和给定的参数列表选择匹配度最高的一个。

个人建议,真的仅仅是个人的一些建议:

1.除及特殊情况外只保留一个构造函数,以省去多余的构造函数匹配检查。

2.在构造函数中注入所需依赖而不是Invoke中。

3.关于构造函数参数的顺序,把RequestDelegate放在第一个;之后是UseMiddleware方法中给出的参数,而且构造函数中参数顺序和给定参数列表中的顺序最好也相同;然后是需要注入的参数;最后是有默认值的参数。以上除了默认值参数必须放在最后外其余的顺序都不是必须的,但按照上面的顺序会比较清晰,而且能使实例创建的开销最小。

4.Invoke方法只保留一个HttpContext参数。这样可以省去对Invoke方法的二次包装。

5.进一步扩展ApplicationBuilder,创建语义更加明确的方法代替Use/UseMiddleware,例如UseMVC、UseStaticFiles。

其中1中所说的及特殊的情况,我能想到的就是给UseMiddleware提供不同的参数列表,进而匹配到不同的构造函数创建实例。具体使用场景没有想到。

0x04 测试

上篇文章中我们写过一个记录后面所有中间件耗时的中间件。当时直接用Use方法注册的。现在我们把它写为一个中间件类,并且把计时功能写为一个StopWatch类,并添加到依赖注入容器中。

下面是计时器类的代码:

下面是中间件类的代码

下面是向依赖注入容器中添加StopWatch

下面是使用UseMiddleware扩展方法添加TimeMiddleware中间件代码

当然,也可以不把StopWatch添加到依赖注入容器中,而是在UserMiddleware方法中直接给出参数。

如果既在依赖注入容器中添加了StopWatch,又在UseMiddleware注册时提供了StopWatch,那么按照参数匹配顺序最终使用的是注册时提供的StopWatch。

运行一下可以看到与上篇文章同样的效果。

0x05 写在最后

UseMiddleware方法使注册中间件变得容易,同事也减小了中间件和其它依赖模块间的耦合。不过不管哪种扩展方法,最终都是通过Use方法实现中间件的注册。下一篇文章将写一下注册中间件的其它扩展方法Map、MapWhen和Run。

0x06 相关文章

.NET Core中间件的注册和管道的构建(1)---- 注册和构建原理

.NET Core中间件的注册和管道的构建(2)---- 用UseMiddleware扩展方法注册中间件类

.NET Core中间件的注册和管道的构建(3) ---- 使用Map/MapWhen扩展方法

.NET Core中间件的注册和管道的构建(2)---- 用UseMiddleware扩展方法注册中间件类的更多相关文章

  1. 用UseMiddleware扩展方法注册中间件类

    用UseMiddleware扩展方法注册中间件类 .NET Core中间件的注册和管道的构建(2)---- 用UseMiddleware扩展方法注册中间件类 0x00 为什么要引入扩展方法 有的中间件 ...

  2. .NET Core中间件的注册和管道的构建(3) ---- 使用Map/MapWhen扩展方法

    .NET Core中间件的注册和管道的构建(3) ---- 使用Map/MapWhen扩展方法 0x00 为什么需要Map(MapWhen)扩展 如果业务逻辑比较简单的话,一条主管道就够了,确实用不到 ...

  3. .NET Core中间件的注册和管道的构建(1)---- 注册和构建原理

    .NET Core中间件的注册和管道的构建(1)---- 注册和构建原理 0x00 问题的产生 管道是.NET Core中非常关键的一个概念,很多重要的组件都以中间件的形式存在,包括权限管理.会话管理 ...

  4. 在注册表中查看Windows10系统激活密钥的方法

      1 2 3 4 5 6 7 分步阅读 百度经验:jingyan.baidu.com 激活Windows10系统(非自己使用激活密钥激活的系统)以后,我们不一定清楚激活密钥是什么.如果想查看自己电脑 ...

  5. ASP.NET Core 1.0中的管道-中间件模式

    ASP.NET Core 1.0借鉴了Katana项目的管道设计(Pipeline).日志记录.用户认证.MVC等模块都以中间件(Middleware)的方式注册在管道中.显而易见这样的设计非常松耦合 ...

  6. Core 1.0中的管道-中间件模式

    ASP.NET Core 1.0中的管道-中间件模式 SP.NET Core 1.0借鉴了Katana项目的管道设计(Pipeline).日志记录.用户认证.MVC等模块都以中间件(Middlewar ...

  7. ASP.NET Core MVC 2.x 全面教程_ASP.NET Core MVC 03. 服务注册和管道

    ASP.NET Core MVC 2.x 全面教程_ASP.NET Core MVC 03. 服务注册和管道 语雀: https://www.yuque.com/yuejiangliu/dotnet/ ...

  8. ASP.NET Core 2.0 : 八.图说管道

    本文通过一张GIF动图来继续聊一下ASP.NET Core的请求处理管道,从管道的配置.构建以及请求处理流程等方面做一下详细的研究.(ASP.NET Core系列目录) 一.概述 上文说到,请求是经过 ...

  9. ASP.NET Core 2.0 : 八.图说管道,唐僧扫塔的故事

    本文通过一张GIF动图来继续聊一下ASP.NET Core的请求处理管道,从管道的配置.构建以及请求处理流程等方面做一下详细的研究.(ASP.NET Core系列目录) 一.概述 上文说到,请求是经过 ...

随机推荐

  1. 用scikit-learn进行LDA降维

    在线性判别分析LDA原理总结中,我们对LDA降维的原理做了总结,这里我们就对scikit-learn中LDA的降维使用做一个总结. 1. 对scikit-learn中LDA类概述 在scikit-le ...

  2. 在 ML2 中配置 OVS flat network - 每天5分钟玩转 OpenStack(133)

    前面讨论了 OVS local network,今天开始学习 flat network. flat network 是不带 tag 的网络,宿主机的物理网卡通过网桥与 flat network 连接, ...

  3. 菜鸟学Struts2——Struts工作原理

    在完成Struts2的HelloWorld后,对Struts2的工作原理进行学习.Struts2框架可以按照模块来划分为Servlet Filters,Struts核心模块,拦截器和用户实现部分,其中 ...

  4. ASP.NET MVC5+EF6+EasyUI 后台管理系统(55)-Web打印

    系列目录 前言 1.本次主要弥补工作流,用户表单数据的打印 2.使用JQprint做为web打印插件 3.兼容:FireFox,Chrome,IE. 4.没有依赖也没有配置,使用简单 代码下载:htt ...

  5. ASP.NET MVC5+EF6+EasyUI 后台管理系统(68)-微信公众平台开发- 资源环境准备

    系列目录 前言: 本次将学习扩展企业微信公众号功能,微信公众号也是企业流量及品牌推广的主要途径,所谓工欲善其事必先利其器,调试微信必须把程序发布外网环境,导致调试速度太慢,太麻烦! 我们需要准备妥当才 ...

  6. 从零开始编写自己的C#框架(28)——建模、架构与框架

    文章写到这里,我一直在犹豫是继续写针对中小型框架的设计还是写些框架设计上的进阶方面的内容?对于中小型系统来说,只要将前面的内容进行一下细化,写上二三十章具体开发上的细节,来说明这个通用框架怎么开发的就 ...

  7. UWP开发之ORM实践:如何使用Entity Framework Core做SQLite数据持久层?

    选择SQLite的理由 在做UWP开发的时候我们首选的本地数据库一般都是Sqlite,我以前也不知道为啥?后来仔细研究了一下也是有原因的: 1,微软做的UWP应用大部分也是用Sqlite.或者说是微软 ...

  8. 将DataTable中的某列转换成数组或者List

    string[] arrRate = dtRate.AsEnumerable().Select(d => d.Field<string>("arry")).ToA ...

  9. Lind.DDD.LindMQ~关于持久化到Redis的消息格式

    回到目录 关于持久化到Redis的消息格式,主要是说在Broker上把消息持久化的过程中,需要存储哪些类型的消息,因为我们的消息是分topic的,而每个topic又有若干个queue组成,而我们的to ...

  10. 推荐10款超级有趣的HTML5小游戏

    HTML5的发展速度比任何人的都想像都要更快.更加强大有效的和专业的解决方案已经被开发......甚至在游戏世界中!这里跟大家分享有10款超级趣味的HTML5游戏,希望大家能够喜欢! Kern Typ ...