前三章主要从理论角度讲述了依赖注入原则及所衍生实现技术和手段产生的原因、发展和当前所处状况。大部分开发人为前三章对自己的技术水平的提高设有立竿见影的直接作用,但的我在里还是要说一下,前三章不但可以使开发者对更加容易的理解.NetCore框架,也是让开发者知道微软为什么会在已经有了一个成熟、使用范围广的.NetFramework框架时还会推出一个新的.NetCore框架历史和技术的重要因素。再者前三章理论性的知识在10年后对开发者或许还有指导作用,以下关于技术方面的具体实现或许因为对当前技术有着重要的指导性,显得极为实用且重要,但随着技术的快速发展极因为技术的落伍不再具有指导性,从而有可能在10年后变的一文不名。并且只重视技术实现,你可能连点状的技术也极难获取,但是如果重视理论不但可以以相对容易获取系统性性的知识,还可能拔高技术提升的天花板。
微软推出.NetCore框架主要因素有两个:
.NetCore框架最初只是为开发基于网络的程序提供基础支撑,所以在.NetCore3.x框架之前是不支撑WInform开发的,.NetCore3.x框架虽然支撑WInform开发但是相比.NetFramework框架来说支撑能力还相对较弱,至今为止(2021年4月12日)与.NetCore3.x框架同时推出.Net5框架也是不支撑WInform开发的,.NetCore框架暴漏出来的各种规范、方法和中间件,就是为了来处理请求(终端客户)应答(服务器)机制提供基础且基本的操作方法,所以通过对暴漏出来各种对象处理流程和功能的理解,就能对整个.NetCore框架有一个很好的理解。
一、.NetCore框架请求(终端客户)应答(服务器)机制的基础支撑原理
由于云计算的兴起,并成为主流,终端客户设备的性能好坏标准的衡量已经从计算性能的提升转移到信息通信、流量处理和最终结果的呈现上,这种现象在手机这种终端客户设备尤其明显,除了手游这种极端的应用还需要一定的计算能力外,其它的基本是绝大部分的应用场景是发送讲求和获取呈现结果(应答)上,而在请求与呈现之间的必须的资源整合操作和计算则是由服务器来完成的。.NetCore框架被微软设计出来的最初目的除为了一般网络应用软件的开发提供一揽子的解决方法、处理流程和规范,本人猜测.NetCore框架另个目的是为了更好的适应对云计算应用的开发。不管是一般网络应用软件还是云计算,不管是开发工具、环境还是基本框架所需要做到的两个基本功能一是对信息的处理这里包含终端客户设备发送的请求和最终形成的结果的发送,另一个是在服务器端计算和处理终端客户设备发送的请求,并把计算结果发送给终端客户设备。
.NetCore框架通过HttpContext来实现和处理终端客户设备发送的请求和最终形成的结果的发送(应答),通过Hosting在服务器端把请求所需要的一切资源(这些资源一般存储在服务器上),按照.NetCore框架提供的决方法、处理流程和规范进行整合操作和计算后把最终结果发送给终端客户设备。由于信息处理相对于资源的整合操作和计算简单,处理方式统一,基本上不需要开发者做其它的额外工作,所以对它们操作基本由.NetCore框架自动完成,只提供极有限的一些属性或参数供开发人员使用。由于需要整合不同的资源,完成各不相同,且复杂多变的操作,所以.NetCore框架提供支撑的重点也是开发人员工作的重点--服务器端,既以Hosting为依托所定义的方法、处理流程、规范和内置中间件。
如果你阅读了上述因素,就很容易理解.NetCore框架在开发环境所定义的三种模板(空白模板、Web模板、MVC模板,这三种模板是相互递进且可以相互转化的,它是目标是让开发者根据需要减少工作量,且空白模板是可以像控件台一样工作的)中的Program.cs文件中的所有定义就是为构造一个(虚拟或软件)主机(Host-主机、托管、服务器),并启动或运行该主机,当该程序的启动项被部署在IIS中时,那么部署该程序的启动项物理主机就是真正的主机、真正的服务器端。实际上开发者使用开发环境进行开时,所有的工作都是由.NetCore框架虚构了一个请求应答(浏览器)和整合计算处理(NetCore框架)环境。由于.NetCore框架虚拟或软件化了主机,所以它是可以独立于IIS而启动或运行的,但是在真正部署是还是需要部署在IIS中的,但部署后的整个运行机制与.NET Framework有着本质差异。
通过.NetCore框架默认提供的Configure方法来响应客户端发送过来的HTTP请求(浏览器),同时开发者根据请求,会根据需要使用.NetCore框架提供的相应的内置中间件或自定义中间件来调用、整合服务器端各种资源、处理结果通过显式的或隐式(cookiet等)的方式信息或计算结果发送到客户端上(浏览器),本人通过开发实践认为.NetCore框架默认提供的Configure方法本身就是最外层最在一个中间件(这一点是灯下黑,是开发者最难发现和容易忽略的),它象俄罗斯套娃一样包含着.NetCore框架提供的内置中间件或自定义中间件(这是本人在实践开发中对Configure方法的功能时行的推理理解,并不是根据源代码所得出的准确的理解,如果通过.NetCore框架源代码有更为准确的理解,请在评论区说明)。且Configure方法.NetCore框架中是一个必须的方法
1、中间件:
以下是本人能从网络上的到的最为通俗易懂、和最为全面的关于中间单件解释和定义:
中间件(Middleware)最初是一个机械上的概念,说的是两个不同的运动结构中间的连接件。后来这个概念延伸到软件行业,大家把应用操作系统和电脑硬件之间过渡的软件或系统称之为中间件,比方驱动程序,就是一个典型的中间件。再后来,这个概念就泛开了,任何用来连接两个不同系统的东西,都被叫做中间件。
所以,中间件只是一个名词,不用太在意,实际代码跟他这个词,也没太大关系。
中间件技术,早在.Net framework时期就有,只不过,那时候它不是Microsoft官方的东西,是一个叫OWIN的三方框架下的实现。到了.Net core,Microsoft才把中间件完整加到框架里来。
感觉上,应该是Core参考了OWIN的中间件技术(猜的,未求证)。在实现方式上,两个框架下的中间件没什么区别。
下面是本人是中间件的理解:
中间件可能是一个方法的实现,可能是定义在一个类中的一组方法的实现,也可能是定义一个框架或架构(经常见到或被使用的例如有:Entity Framework,Entity FrameworkCore、AutoFac等)中的所有方法的实现。所以从功能范围上来判断是否为中间件这个名词可能是空泛和模糊的,因为从广义的范围来说所有存在于三界和五行内的事物都是中间件,但是从最小细粒度方法上来判断是否为中间件确是可以的。这些统统被冠名为中间件的方法,特征或属性包括:以特定方式进行定义,完成一些具有统一或相似操作功能的特定的方法。这些方法它的特定的定义方式实际上与WinFrom开发中一些自定控件的定义格式和相似性实现功能雷同,例如:关键字 static、this等,如果这些方法被定义在一个类中那么这个类也必须被关键字 static所限定。实际上本人推测中间件方法的定义格式和实现功能的相似性实际上是照搬了WinFrom对控件的定义格式和实现功能的相似性。
public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
= public IApplicationBuilder Use(Func<RequestDelegate(委托【1】), RequestDelegate(委托【2】)> middleware)
= public static IApplicationBuilder Use(this IApplicationBuilder app, (委托【1】)Func<HttpContext=in T1, (委托【2】)Func<Task> =in T2, Task =out TResult> middleware)
.NetCore框架提供的Configure方法和内置中间件,开发者只是使用不追求其运行原理,会极其快的上手,且能在其基础上开发出不错的软件,但是想更进一步,天花板已经存在。想更上一层楼理解Configure方法和内置中间件运行原理是必由之路。通过2中给出的.NetCore框架中中间件方法定义,可以直接被看出的所使用的技术有:
语言的高级应用有:
(1)、委托
(2)func委托
(3)多线程
(4)异步
(5)泛型
数据结构应用有:
Configure方法通过两个参数,这两个参数是以线程委托方法使用双向链表的形式对多个定义在Configure方法中的中间件方法进行加载操作,由于.NetCore框架的实践原理之一是请求->应答机制,所以同时在执行完成后的回溯操作也必须由双向链表来实现和完成。
通过中间件定义方法可以本人所能直接看出的所使用的技术就有以上这些,这只是Configure方法实现难度的表层,从这表层就能体现出微软那么大牛对开发语言和数据结构的灵活和强大的运用能力,这些强大的能力真是让人绝望,就想《水调用歌头》中的每个汉字可能小学三年级都能认全,但能写出的在中国这文化强国,文化历史漫长的国度,特别是古代更甚才有一篇。这里并不是让开发者去以走火如魔的灵活和强大方法(1、必须有需求、2、对大环境有意义),而是便于对Configure方法和中间件实现难度和灵活性的理解。其中可以根据工程中所实现功能的需要去灵活的选择相应的内置中间件,例如VisualStuido开发环境就根据.NetCore框架和使用需求给出了三种不同的应用模板:空、Web和MVC,它们最大的不同在于.NetCore框架出的ConfigureServices和Configure方法中定义了相应功能的中间件,且三种不同的应用模板是可能通过在ConfigureServices和Configure方法中添加相应功能的中间件方法是可以所以把最为 简单的“空”模板,定义为最为复杂的“MVC”模板的。
图4
图5
图4、图5是微软官方(https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/middleware/?view=aspnetcore-3.1#create-a-middleware-pipeline-with-iapplicationbuilder)根据最为复杂的“MVC”模板给出的内置中间件在Configure方法中的运行原理图,如果不求甚解,这两张原理图的确有此用处,但是如果深入理解这两原理图会对开发者的理解产生干扰甚至歧义。
如图4内置中间件方法在Configure方法中的执行过程就会对开发者的理解产生干扰,内置中间件方法不是都必须在Configure方法中时行定义的,而是根据需求调用的,像使用“空”模板时Configure方法中默认给出的内置中间件方法只有两个,而不是像图4那样程序的执行或运行必须执行所有的内置中间件方法。
另外通过开发实践发现Configure方法中的中间件方法在执行或运行的过程中是被执行或运行两次的,这令我苦恼的很长时间,通过我多次实践我推理认为真正的中间件方法在使用多项技术实现的Configure方法中的执行或运行原理并不像如图4描述的那样简单明了,甚至会给开发者深入理解造成干扰,本人认为其运行原理如下图所示(可从https://download.csdn.net/download/zhoujian_911/18384603下载图6):
图6
图6彻底解释了为什么中间件方法会在Configure方法中被执行两次,如果开发者理解了图6,那么将会打破.NetCore框架的认知天花板,使用起来虽然不像微软大牛一样灵活、强大,但是更加轻松、容易这样的目标还是能够达到的。
因为整个.NetCore框架的实现是基于依赖注入思想的实践,所以数据库操作方法必须在ConfigureServices方法中进行依赖注入的定义,由于数据库操作方法的依赖注入在程序开发中是常用功能所以.NetCore框架给出了在ConfigureServices方法中进行依赖注入的定义的内置中间件方法,但是为什么在Configure方法中.NetCore框架没有给出与数据库操作相关的内置中间件方法呢?实际上通过耐心的观察就能很容易的发出Configure方法中的的件间方法都是需要跨域请求(客户端)->处理、计算、整合(服务器端)->应答(客户端)整个过程的,但是对数据库操作方法的处理计算、整合都发生在服务器端,只是最终把数据库的一些结果或数据通过.NetCore框架中或开发者自定义的操作方法呈现在客户端,因此对数据库这些底层资源的操作只涉及服务器和开发者,所有只需且必须在ConfigureServices方法中添加关于数据库操作的内置中间件方法。而.NetCore框架无须为Configure方法提供相应的内置中间件方法。以下本人能从网上找到的一些关于中间件方法在Configure 方法执行原理的方法示意图,以帮助大家进一步的加深理解。
图7(Asp.Net Core EndPoint 终结点路由工作原理解读)
图8(UseRouting() 、UseAuthorization()、UseEndpoints() 三中间件关系)
首先看一下RequestDelegate这个方法:
RequestDelegate requestDelegate = (context) =>
{
var routeData = context.GetRouteData();
var actionContext = new ActionContext(context, routeData, action);
var invoker = _invokerFactory.CreateInvoker(actionContext);
return invoker.InvokeAsync();
};
将这个方法的内容分为两部分:
A. invoker的生成,前三句,通过CreateInvoker方法生成了一个invoker,它是一个比较复杂的综合体,包含了controller的创建工厂、参数的绑定方法以及本action相关的各种Filter的集合等, 也就是说它是前期准备工作阶段,这个阶段扩展开来涉及面比较广,在下文详细描述。
B.invoker的执行,最后一句,前面已经万事俱备,准备了好多方法,到此来执行。此时涉及到前面准备的各种方法的执行,各种Filter也在此时被执行。
首先说一下此图的结构,每个泳道相当于是上一个泳道中的
图标的细化说明,例如第二条泳道是图标①标识的方块的明细化。
图9(invoker的执行)
此图描述了请求经过其他中间件处理后,进入路由处理最终找到了对应的action,最终进入筛选器管道进行处理。而这个处理的核心部分就是方法中的 while (!isCompleted) 循环,它对应的Next方法比较长,如下(较长已折叠
图10(action的执行)
图11(Wrapped-RequestDelegate-Pipeline,红色部分由本人添加)
网络上充实着大量关于ConfigureServices和Configure方法解释和说明,但是他们并没有把ConfigureServices和Configure方法解释和说明通和透,要相把ConfigureServices和Configure方法解释和说明通和透,需要从以下几点进行解释和说明:
(1)、如果开发者使用.NetCore框架进行软件的开发,.NetCore框架仅且只暴露或提供了两个方法,这两个方法就是ConfigureServices和Configure方法,以供开发者使用。只要使用.NetCore框架进行软件的开发,开发者所自定义方法或调用内置中间件在执行的起始阶段和回溯结束阶段最终都会回归到这两个方法中。
(2)、在正常的程序开发中,类对象的声明和实例化操作是通过”new”关键字框架直接实现的,其实例化过程和方式简单明了。为了降低程序中对象间的耦合程度(在软件工程中不能说或者不存在对耦合进行减少或增加,只能说耦合程度的减少或增加或者是耦合对象的迁移:从类的具体实现迁移到类所继承的接口),根据依赖注入思想(这是.NetCore框架实践的另一个基本理)的实践.NetCore框架实现了该目标,但是目标的实现早已标定了价格,就是类对象的声明和实例化操作过程和方式变的困难和复杂,.NetCore框架使用ConfigureServices方法来实现依赖注入方式下的类对象的声明和实例化操作,在默认情况下.NetCore框架把类对象的声明和实例化操作分为了两个操作过程:注册操作过程和实例化操作过程,它们分别由.NetCore框架提供的内置方法来分别实现和完成。实现注册操作过程的内置方法分别有:AddSingleton、AddTransient和AddScoped方法它们之间的区别在于类对象在实例化后的,实例对象存在于整个程序执行过程中的生命周期不同,这在网络上有着大量的说明和实例。实例化操作过程分别有:一种是在相应的构造方法中直接实例化,另一种是使用内置类对象IServiceProvider 中指定方法。到此为止类对象的声明和实例化操作,虽然变的相对于”new”关键字来说有了一定复杂性但是在理解上还是相对容易的。但是在实际开发中开发者可能调用大量的内置中间件方法、第三方法中间件方法和大量的自定义方法,这些对象统统需要分别被注册和实例化操作,为了能够对这些对象进行统一和集中的管理,.NetCore框架提供了一个内置的容器(IServiceCollection)来盛放(注册)这些对象,注意: 容器只用于盛放(注册)实例的注册相当于常规操作中的声明,它只是告诉整个程序,类对象的实例化流程符合依赖注入思想实践标准,已经可以参与于整个程序运行的体制了,所以在.NetCore框架中最常遇到的逻辑异常就是在间接调用实例对象的情况下不经过注册就直接使用对象实例,至于实例化操作依然需要经过上述方式来实现。在.NetCore框架中另一个被经常用到的第三方中间件容器“Autofac”,“Autofac” 中间件容器包含有一套自己定义的用于类对象进行注册并对实例对象生命周期管理、解析的方法集,如果类对象已经在被 “Autofac” 中间件容器中的指定方法注册过那么,它的实例化操作最为常用的依然是通过相应的构造方法来实现,但是在实例化前需要通过“Autofac” 中间件容器中的指定方法进行解析,开发者可以根据需求来自定义和扩展“Autofac” 中间件容器中的解析方法。在实际开发中会存在着大量需要被盛放(注册)到的类对象,开发者以给出了用于把类对象批量盛放(注册)容器中的操作方法最常用的两种分别是:反射注入和范型注入,同样在网络中也存在着大量的关于反射注入和范型注入的技术实现在这里不再赘述,到现在类对象的实例化过程对于”new”关键字来说已经经过了不同的技术实现和层层的封装,这也是使用.NetCore框架进行开发比常规方式进行开发难以理解的原因之一。综上所述.NetCore框架对ConfigureServices方法所赋予的使命有:注册(让类对象符合依赖注入思想体制);实例化后的实例对象的生命周期管理由,特定的注册方法指定;为了统一和集中管理把类对象注册到ConfigureServices方法指定的容器中;最后类对象的实例化操作是必须经过ConfigureServices方法调用特定方法进行注册过的,否则该类对象实例化流程就不符合依赖注入思想的标准,最终达到迁移耦合对象从而降低耦合程度的目标。即ConfigureServices方法实现的目标是降低耦合程度,实现的依据是依赖注入思想,而最终的实践是ConfigureServices方法、特定的注册方法、容器和相应的构造方法或特定方法来实现一些对象(这些对象所包含的类型有,.NetCore框架的内置中间件方法、第三方中间件方法和开发者 自定义方法)的实例化操作。
(3)、ConfigureServices方法在整个程序的运行流程中只存在于服务器端,它所针对的对象只有开发者,所以在ConfigureServices方法中所定义或调用的内置中间件方法(例如:上述所说的数据库内置中间件方法),在Configure方法就不是必须被定义或调用的,但是Configure方法中所定义或调用的内置中间件方法,在ConfigureServices方法就必须进行定义或调用,因为Configure方法中所定义或调用的内置中间件方法在整个程序的运行流程是客户端->服务器->客户端,它同时针对开发者和客户,开发者需要通过一定的操作来实现客户所需的呈现,而这些操作又必须使用ConfigureServices方法调用的指定方法进行注册后才能被实例化,这是定义在Configure方法中的中间件方法也必须定义在ConfigureServices方法中的原因,同时它还是.NetCore框架默认在Startup 类的定义中可选择性地包括 ConfigureServices 方法以配置应用的服务,而必须包括 Configure 方法以创建应用的请求处理管道的原因。
实际上对.NetCore框架的理解只要把握两个大方向:
(1)、.NetCore框架首先是请求->应答机制的实践,这是.NetCore框架业务需求和功能需求,这也.NetCore框架被开发出来的根本因素,这同样也是必须包括 Configure 方法以创建应用的请求处理管道的原因。
(2)、为了最终迁移耦合对象从而降低耦合程度的目标,在对.NetCore框架开发的过程中根据依赖注入思想,使用各种技术手段实现了该目标,这是.NetCore框架实现的技术需求,它是伴生需求,同时.NetCore框架为了保障实现技术必须符合依赖注入思想,.NetCore框架通过ConfigureServices方法强制让开发者的定义或调用适应.NetCore框架被设计之初所必须遵循的开发思想和技术规范,这同样也是可选择性地包括 ConfigureServices 方法以配置应用的服务的原因。
综上所述出于不同的需求导致最初差异化的设计,从而在最终的技术实现上因为被.NetCore框架定义为各分别管理不同的操作,从而造成ConfigureServices方法和Configure方法及具有不能特性和功能,以及相互配合的根本因素。