.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。

原文地址:http://www.cnblogs.com/durow/p/5748124.html


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/327993.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring Boot进阶之Web进阶 代码推送的github上面去

还是搜狗的输入法比较好 Exception.class 上面开不见的部分是这里的 代码上次github上面去保存起来 https://github.com/yangjiabinylg/girl2 https://github.com/yangjiabinylg/girl2 全部做完了 代码提交到github 上面去了

JVM初探- 使用堆外内存减少Full GC

转载自 JVM初探- 使用堆外内存减少Full GC问题: 大部分主流互联网企业线上Server JVM选用了CMS收集器(如Taobao、LinkedIn、Vdian), 虽然CMS可与用户线程并发GC以降低STW时间, 但它也并非十分完美, 尤其是当出现Concurrent Mode Failure由并行GC转入串行时, 将导致非常长时间的…

快捷生成---QQ点击联系我的方法

第一步 第二步 第三步 把uin2764954910 p2:2764954910:53 换成自己的QQ号 运行 完结撒花

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

0x00 问题的产生 管道是.NET Core中非常关键的一个概念&#xff0c;很多重要的组件都以中间件的形式存在&#xff0c;包括权限管理、会话管理、路由等。所以搞明白中间件是如何注册并最终构建成管道的很重要。园子里很多先驱早已经开始了这方面的研究学习&#xff0c;也写了很多…

语言 高速公路超速处罚_重磅!全国高速将统一限速,这4种超速不再扣分罚款!【饮茶论道】...

在高速开车&#xff0c;经常会经历“断崖式降速”和“忽高忽低式限速”。相信不少吃了罚单的司机感受都是&#xff1a;哑巴吃黄连——有苦说不出……现在&#xff0c;重磅消息来啦&#xff01;在高速公路上行驶前方没有任何障碍导航却突然提醒你“当前道路限速60km/h&#xff0…

SpringBoot开发常用技术整合 代码上传至github上面去

简介&#xff1a;本课程通过详细的对springboot的各个技能点逐一介绍与演示&#xff0c;可以很迅速的熟悉整个springboot框架体系&#xff0c;并且与springmvc有效的进行对比&#xff0c;理解异同&#xff0c;这样对于后续的springboot开发会非常迅速。同时课程中会针对不同的技…

Java中对象的三种状态

转载自 Java中对象的三种状态Java中的对象的三种状态是和垃圾回收紧密相关的&#xff0c;因此有必要深究。 状态一&#xff1a;可触及态&#xff1a;从根节点开始&#xff0c;可以搜索到这个对象&#xff0c;也就是可以访问到这个对象&#xff0c;也有人将其称为可达状态。 状…

HTML5(笔记)

什么是HTML Hyper Text Markup Language(超文本标记语言) 超文本包括&#xff1a;文字&#xff0c;图片&#xff0c;音频&#xff0c;视频&#xff0c;动画等 w3c标准 WOrld Wide Web Consortium(万维网联盟) 成立于1994年&#xff0c;Web技术领域最权威和具影响力的国际中…

训练测试数据大小不一致_三步学会训练狗狗不随地大小便

训练狗狗在规定的地点大小便是非常重要的训练&#xff0c;它决定了你的屋子和院子能否干净整洁。如果是室内训练&#xff0c;我要先告诉你一些相关的训练禁忌。首先&#xff0c;当狗狗在家里排便之后才对狗狗做出惩罚&#xff0c;是最普遍的一个训练错误&#xff0c;这只会使问…

使用实体框架、Dapper和Chain的仓储模式实现策略

关键要点&#xff1a; Dapper这类微ORM&#xff08;Micro-ORM&#xff09;虽然提供了最好的性能&#xff0c;但也需要去做最多的工作。在无需复杂对象图时&#xff0c;Chain这类Fluent ORM更易于使用。对实体框架&#xff08;Entity Framework&#xff09;做大量的工作后&#…

JVM-对象的存活与死亡

转载自 JVM-对象的存活与死亡 当Java虚拟机进行垃圾收集的时候&#xff0c;那么它必须要先判断对象&#xff0c;是否还存活&#xff0c;如果存活就不能对它进行回收。所以判断一个对象是否存活是Java虚拟机必须要实现的。1.对象是否存活  1&#xff09;引用计数器&#xff1…

SpringBoot+MyBatis搭建迷你小程序

简介&#xff1a;用Spring Boot框架大大简化了新Spring应用的初始搭建以及开发过程&#xff0c;在开发人员中越来越受到欢迎。微信小程序作为目前炙手可热的应用&#xff0c;很有可能在未来占据轻应用的市场。本门课程的主要目的是将两者结合起来&#xff0c;同时希望作为入门翔…

蓝桥杯JAVA省赛2013-----B------3(振兴中华)

【解析】&#xff1a;将格子中的字存放到一个二维数组中&#xff0c;使用回溯法依次进行遍历&#xff0c; 符合条件的1&#xff0c;最后求出总和 【答案】&#xff1a;35 从我做起振 (0, 0) (0, 1) (0, 2) (0, 3) (0, 4) 我做起振兴 (1, 0) (1, 1) (1, 2) (1, 3) (1, 4) 做起…

python变量后面加星号_Python基础找茬系列20--python函数的秘密

一、小试牛刀二、函数的定义def 函数名(参数列表): 函数体【1】函数的关键词&#xff1a;是def&#xff0c;不是del&#xff0c;也不是function【2】函数的名称&#xff1a;不能使用关键词作为函数的名称&#xff0c;允许使用内置函数名作为函数名称&#xff0c;这会覆盖内置函…

基于Quartz.net 的开源任务管理平台

最近&#xff0c;又重新整理&#xff0c;开发出了一套基于Quartz.net 的任务管理平台。将Quartz.net 的任务调度&#xff0c;管理等功能统一整合&#xff0c;形成了一套比较完整的任务调度平台。主要是&#xff1a;任务调度服务&#xff0c;后台任务管理 等功能。 github地址&a…

Java中的垃圾回收与对象生命周期

转载自 Java中的垃圾回收与对象生命周期1. 垃圾回收 垃圾回收是Java程序设计中内存管理的核心概念&#xff0c;JVM的内存管理机制被称为垃圾回收机制。 一个对象创建后被放置在JVM的堆内存中&#xff0c;当永远不再引用这个对象时&#xff0c;它将被JVM在堆内存中回收。…

页面复杂对象传递参数 开发中遇到的问题

左边是我发送的数据 转换成右边的就可以接受到数据了 我发送的数据接收回来 这样发送 服务器就可以接收到数据了

hibernate正向生成数据库表以及配置——hibernate.cfg.xml

<?xml version1.0 encodingUTF-8?> <!DOCTYPE hibernate-configuration PUBLIC"-//Hibernate/Hibernate Configuration DTD 3.0//EN""http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"><!-- Generated by MyEclipse H…

蓝桥杯JAVA省赛2013-----B------4(黄金连分数)

【答案】&#xff1a;0.6180339887498948482045868343656381177203091798057628621354486227052604628189024497072072041893911375 识别问题 --> 斐波那契的第n项与第n1项的比值n要多少才够 --> 精度处理大整数、大浮点数 &#xff1a;add()-&#xff1a;subtract()*&…

.NET仓储模式高级用例

主要结论 如果需要执行基本CURD之外的其他操作&#xff0c;此时就有必要使用仓储&#xff08;Repository&#xff09;。为了促进测试工作并改善可靠性&#xff0c;应将仓储视作可重复使用的库&#xff08;Library&#xff09;。将安全和审计功能放入仓储中可减少Bug并简化应用程…