asp.net core 笔记

此文是阅读Artech《ASP.NET Core 3 框架揭秘》和其他网友的文章记录的笔记,尽管ASP.NET Core 6正式版已经发行,并不觉得这本书会过时淘汰,相反现阶段本人认为是从早期的ASP.NET转移升级到ASP.NET Core的最佳时机,不早也不晚成本最低。一直都在关注ASP.NET Core,所以并不陌生,在.NET4.x中就使用另一种类似的技术Katana和Owin,ASP.NET Core发布后,Katana和Owin完成了使命退出。如果说ASP.NET Core最核心的两件事就是依赖注入,以及自宿主Kestrel摆脱了IIS,自此不用担心跨平台部署的需求了。


特别说明:下述内容笔记是结合.NET4.x中的知识理解的,毕竟是一个使用.NET/C#编程十多年.Neter。


1.依赖注入(DI)
1.1.简述:就是让框架来管理对象创建和销毁,不用你自己管理它们,你只负责使用它们就行。
1.2.服务实例的生命周期模式:Singleton、Scoped和Transient
1.2.1.Singleton:单例对象,相当于单例模式创建的对象或全局的静态对象,IServiceProvider对象创建的服务实例由根容器对象保管,所以同根的IServiceProvider对象针对同一类型的服务实例,提供的都是同一个对象。
1.2.2.Scoped:服务范围对象,IServiceProvider对象创建的服务实例由自己保管,所以同一个IServiceProvider对象针对同一类型的服务实例,提供的均是同一个对象。
1.2.3.Transient:临时对象,即用即建,用后即弃。针对每一次服务请求,IServiceProvider对象总会创建一个新的服务实例
1.3.服务注册、服务消费和服务释放
1.3.1.服务注册
  避免Singleton服务实例将Scoped服务实例变成Singleton模式,从而导致无法及时释放,在开发模式中一定要开启验证检查。
1.3.2.服务消费
  如果服务实例有多个构造函数,那么服务实例在创建选择构造函数时,就会遵循以下规则:每一个候选构造函数的参数类型集合都是要选定的构造函数参数类型的子集,也就是说优先选择所有候选构造函数参数类型集合超集的构造函数,作为选定的构造函数。如果构造函数中不存在这种子集或超集的关系,那么会抛出异常。
1.3.3.服务释放
  服务实例释放,是指实现了接口IDisposable或IAsyncDisposable,在服务实例用完之后,系统会调用Dispose或DisposeAsync方法。如果没有实现这个接口,就要等着GC来回收了。
1.4.关系概述
1.4.1.ServiceProviderEngine
    a)关系1:IServiceCollection=>IServiceProvider=>ServiceProviderEngine
    b)关系2:IServiceCollection对象的BuildServiceProvider方法创建了实现接口IServiceProvider的ServiceProviderEngine对象。
    c)关系3:ServiceProviderEngine实现了接口IServiceProvider,所以它是一个依赖注入容器。
    d)关系4:ServiceProviderEngine实现了接口IServiceScopeFactory,它的属性RootScope返回一个IServiceScope对象,它的方法CreateScope返回一个IServiceScope对象。
    e)关系5:ServiceProviderEngineScope实现了接口IServiceScope,所以上一步中返回的IServiceScope对象,可以理解为ServiceProviderEngineScope对象。

1.4.2.ServiceProviderEngineScope
    a)关系1:ServiceProviderEngineScope不仅实现了接口IServiceScope,还实现了接口IServiceProvider,所以可以视为IServiceScope对象是对IServiceProvider对象的封装。
    b)关系2:ServiceProviderEngineScope的属性ServiceProvider,返回的就是它自己。
    c)关系3:ServiceProviderEngineScope对象是由方法CreateScope创建。

1.4.3.ServiceProviderEngine种类
    a)RuntimeServiceProviderEngine:采用反射的方式提供服务实例。
    b)ILEmitServiceProviderEngine:采用IL Emit的方式提供服务实例。
    c)ExpressionsServiceProviderEngine:采用表达式树的方式提供服务实例。
    d)DynamicServiceProviderEngine:根据请求并发数量动态决定最终的服务实例提供方案(上述三种之一)。

1.4.4.要点记录
    a)ServiceProviderEngine.GetService方法返回的是RootScope的ServiceProviderEngineScope对象,此时调用ServiceProviderEngineScope对象的GetService方法才会返回自己。
    b)ServiceProviderEngine的属性RootScope维护着Singleton类型的服务。

1.4.5.IServiceScope和IServiceScopeFactory
  IServiceProvider对象利用IServiceScopeFactory创建一个代表服务范围的IServiceScope对象,后者内具有一个新创建的IServiceProvider对象,两个IServiceProvider对象逻辑上具有父子关系,实际上子对象不需要知道父对象是谁,只需要知道根节点的IServiceProvider在哪里就行。
1.5.IServiceProvider对象
  Asp.Net Core中有两类IServiceProvider对象,一种是作为根容器并与应用程序具有相同生命周期的IServiceProvider对象(ApplicationServices),另一种是根据请求及时创建和释放的IServiceProvider对象(RequestServices)
小结:和.Net Framework比较,就当作之前使用工厂模式创建和管理对象,现在改成框架帮你创建和管理对象就好。


2.文件系统
2.1.简述:这里的文件系统是指实现接口IFileProvider对象提供的对目录和文件的只读服务,并且具备监视文件变化的功能。从前后端分离的角度看,此项功能没什么用处,从本地配置文件管理看是有用处的,从MVC开发用处则很大。
2.2.IFileProvider
2.2.1.核心方法
    a)GetFileInfo方法:返回IFileInfo接口对象,用于读取文件内容。
    b)GetDirectoryContents方法:返回IDirectoryContents接口对象,用于查看目录下有多少文件或子目录。
    c)Watch方法:如果IFileProvider对象提供了Watch功能,可以利用他监视目录和文件的变化。
2.2.2.实现种类
    a)PhysicalFileProvider:读取物理硬盘上的目录和文件。
    b)EmbeddedFileProvider:读取内嵌入程序集中的文件。
2.3.IChangeToken
2.3.1.概述:IFileProvider.Watch方法返回的类型。
2.3.2.常用实现
    a)CancellationChangeToken :取消令牌
    b)CompositeChangeToken:代表由多个IChangeToken组合而成的复合型IChangeToken对象
小结:这个相当于一个新的基础类,就是帮你读取和监视文件信息。


3.配置
3.1.核心对象
3.1.1.四大对象
    a)IConfiguration:供开发人员使用
    b)IConfigurationBuilder:构建IConfiguration对象
    c)IConfigurationSource:定义配置数据源
    d)IConfigurationProvider:操作配置数据源
3.1.2.内部对象
    a)IConfigurationRoot:表示根节点配置项
    b)IConfigurationSection:表示某一个节点配置项
    c)ConfigurationReloadToken:通知应用程序,配置源已经发生改变,并且新的数据已经被相应的IConfigurationProvider重新加载进来。
3.2.绑定规则:路径化Key,中间使用冒号(:)分隔
3.3.数据源类型
3.3.1.MemoryConfigurationSource:内存配置源,采用一个字典对象作为存放原始配置数据的容器。
3.3.2.EnvironmentVariablesConfigurationSource:环境变量配置源,按照作用域的不同,环境变量划分成三类,即分别针对当前系统、当前用户和当前进程的环境变量。对于Windows系统来说,系统和用户级别的环境变量保存在注册表中。
3.3.3.CommandLineConfigurationSource:命令行配置源,即通过命令行启动程序时,追加的参数。
3.3.4.FileConfigurationSource:文件配置源,它利用FileProvider对象操作配置文件。
    a)JsonConfigurationSource:Json格式配置源文件
    b)XmlConfigurationSource:Xml格式配置源文件
    c)IniConfigurationSource:Ini键值对格式配置源文件
3.3.5.DbConfigurationSource:数据库配置源,这个就需要自己实现了,所以也称为自定义配置源。
小结:.Net Framework时是不是自己定义程序的配置文件,然后自己读取配置文件信息使用,我以前是这么做的,现在由框架提供的对象和方法接管了。


4.Options
4.1.概念:Options模式是一种采用依赖注入的方式来提供Options对象编程,但这并不意味着我们会直接利用依赖注入框架来提供Options对象本身,因为利用依赖注入框架获取的是一个能够提供Options对象的IOptions<TOptions>对象,泛型参数TOptions表示的正是Options对象的类型。
4.2.配置源同步:Options对象以依赖注入的方式注入到容器后,利用IOptionsMonitor<TOptions>服务,使注入的Options对象得到及时更新。
4.3.核心对象
4.3.1.IOptions<TOptions>接口:提供非具名的Options对象
4.3.2.IOptionsSnapshot<TOptions>接口:提供具名的Options对象
4.3.3.OptionsManager<TOptions>:依赖注入容器IServiceProvider对象,提供的IOptions<TOptions>服务或者IOptionsSnapshot<TOptions>服务,都是通过OptionsManager<TOptions>对象提供的。而OptionsManager<TOptions>对象内部又通过IOptionsFactory<TOptions>和IOptionsMonitorCache<TOptions>对Options对象分别进行创建与缓存。
4.4.Options对象创建
4.4.1.步骤:实例化->初始化->验证
4.4.2.Options对象初始化
    a)IConfigureOptions<in TOptions>:负责初始化默认的Options对象(空字符串命名)
    b)IConfigureNamedOptions<in TOptions>:负责对具名Options对象初始化
    c)IPostConfigureOptions<in TOptions>:对Options对象再加工
4.4.3.Options对象验证:IValidateOptions<TOptions>
4.5.Options对象缓存
4.5.1.简述:缓存Options对象可以获得更好的性能。
4.5.2.对象与关系
    a)OptionsCache<TOptions>:它实现了接口IOptionsMonitorCache<TOptions>,它的核心功能就是对字典(ConcurrentDictionary<string, Lazy<TOptions>>)的管理。
    b)IOptionsMonitorCache<TOptions>之所以被命名如此,因为它主要服务于IOptionsMonitor<TOptions>。
    c)当IOptionsMonitor<TOptions>对象监控到数据变化时,会对外发送通知IChangeToken。IChangeToken对象是由IOptionsChangeTokenSource<TOptions>对象提供的。
4.6.Options对象注入
4.6.1.AddOptions()方法:依赖注入到容器中的对象是由生命周期的,那么Options对象在注入到容器时也要设置生命周期,内容如下。

ServiceType Implementation Lifetime
IOptions<TOptions> OptionsManager<TOptions> Singleton
IOptionsSnapshot<TOptions> OptionsManager<TOptions> Scoped
IOptionsMonitor<TOptions> OptionsMonitor<TOptions> Singleton
IOptionsFactory<TOptions> OptionsFactory<TOptions> Transient
IOptionsMonitorCache<TOptions> OptionsCache<TOptions> Singleton



4.6.2.Configure<TOptions>()方法:注册IConfigureOptions<TOptions>服务
4.6.3.PostConfigure<TOptions>()方法:注册IPostConfigureOptions<TOptions>服务
4.6.4.ConfigureAll<TOptions>()和PostConfigureAll<TOptions>()方法:注册时会将ConfigureNamedOptions<TOptions>和PostConfigureOptions<TOptions>类型的名称置为Null。
4.6.5.ConfigureOptions()方法:对于自定义实现了IConfigureOptions<TOptions>接口或者IPostConfigureOptions<TOptions>接口的类型,则要调用此方法进行注册。
4.6.6.OptionsBuilder<TOptions>对象:上述方法都是基于IServiceCollection接口的扩展方法,最新版本的Options模型采用Builder模式来完成相关的服务注册,即将用来存储服务注册的IServiceCollection集合封装到OptionsBuilder<TOptions>对象中,利用OptionsBuilder<TOptions>对象提供的方法间接地完成所需的服务注册。
小结:我的理解是“你可以把Options模型当作是DI的实践应用,也可以当作是把配置信息DI化”,因为对于AspNetCore来说一切皆DI。


5.服务承载系统
5.1.简述:.NET Core提供了承载(Hosting)系统,我们可以在它之上寄宿多个长时间运行的服务,任何需要在后台长时间运行的操作都可以定义成标准化的服务并利用该系统来承载。这么说好像还是不够直观,.NET4.x中创建过“系统服务”类型的程序吧,就是有Start和Stop方法运行在后台的程序,就当作是它好了。其中有两个概念:承载系统(Hosting)和承载服务(HostedService)。
5.2.核心对象
5.2.1.承载服务(IHostedService)
5.2.1.1.定义:承载服务通过IHostedService接口表示,该接口定义的StartAsync方法和StopAsync方法可以启动与关闭服务。
5.2.1.2.理解:一个ASP.NET Core应用本质上是一个需要长时间运行的服务,开启这个服务是为了启动一个网络监听器。当监听到抵达的HTTP请求之后,该监听器会将请求传递给应用提供的管道进行处理。管道完成了对请求处理之后会生成HTTP响应。
5.2.1.3.启停:当作为宿主的IHost对象被启动的时候,它会利用依赖注入框架激活每个注册的IHostedService服务,并通过调用StartAsync方法来启动它们。当服务承载应用程序关闭的时候,作为服务宿主的IHost对象会被关闭,由它承载的每个IHostedService服务对象的StopAsync方法也随之被调用。
5.2.1.4.注册:两种注册方式
    1)ConfigureServices(svcs => svcs.AddSingleton<IHostedService,xxxxHostedService>())
    2)ConfigureServices(svcs => svcs.AddHostedService<xxxxHostedService>>())
5.2.2.承载系统(IHost)
5.2.2.1.定义:承载服务最终被承载于通过IHost接口表示的宿主上。一般来说,一个服务承载应用在整个生命周期内只会创建一个IHost对象,我们启动和关闭应用程序本质上就是启动和关闭作为宿主的IHost对象。
5.2.2.2.Services属性:IHost接口的Services属性返回作为依赖注入容器的IServiceProvider对象,该对象提供了服务承载过程中所需的服务实例,其中就包括需要承载的IHostedService服务。
5.2.2.3.Run扩展方法:调用IHost对象的扩展方法Run,它会在内部调用StartAsync方法,接下来它会持续等待下去直到接收到应用被关闭的通知。
5.2.3.应用生命周期(IHostApplicationLifetime)
5.2.3.1.定义:IHostApplicationLifetime接口体现了服务承载应用程序的生命周期,该接口除了提供了三个CancellationToken类型的属性来检测应用何时开启与关闭之外,还提供了一个StopApplication方法来关闭应用程序。
5.2.3.2.停止Run:当IHost对象利用IHostApplicationLifetime服务接收到关于应用关闭的通知后,它会调用自身的StopAsync方法,针对Run方法的调用此时才会返回。启动IHost对象直到应用关闭这一实现体现在HostingAbstractionsHostExtensions.WaitForShutdownAsync扩展方法上。
5.2.4.宿主构建者(IHostBuilder)
5.2.4.1.定义:IHostBuilder接口的核心方法Build用来提供由它构建的IHost对象,在构建对象之前会做前期设置。
5.2.4.2.配置:IHostBuilder接口针对配置系统的设置体现在ConfigureHostConfiguration和ConfigureAppConfiguration方法上。ConfigureHostConfiguration方法涉及的配置主要是在服务承载过程中使用的,是针对服务宿主的配置;ConfigureAppConfiguration方法设置的则是供承载的IHostedService服务使用的,是针对应用的配置。不过前者最终会合并到后者之中,最终得到的配置实际上是两者合并的结果。
5.3.启动流程:实际上HostBuilder对象并没有在实现的Build方法中调用构造函数来创建Host对象,该对象利用作为依赖注入容器的IServiceProvider对象创建的。为了可以采用依赖注入框架来提供构建的Host对象,HostBuilder必须完成前期的服务注册工作。总地来说,HostBuilder针对Host对象的构建大体可以划分为5个步骤
5.3.1.创建HostBuilderContext上下文:创建针对宿主配置的IConfiguration对象和表示承载环境的IHostEnvironment对象,然后利用二者创建出代表承载上下文的HostBuilderContext对象。
5.3.2.创建针对应用的配置:创建针对应用配置的IConfiguration对象,并用它替换HostBuilderContext对象承载的配置。
5.3.3.注册依赖服务:注册所需的依赖服务,包括应用程序通过调用ConfigureServices方法提供的服务注册和其他一些确保服务承载正常执行的默认服务注册。
5.3.4.创建IServiceProvider:利用注册的IServiceProviderFactory<TContainerBuilder>工厂(系统默认注册或者应用程序显式注册)创建出用来提供所有依赖服务的IServiceProvider对象。
5.3.5.创建Host对象:利用IServiceProvider对象提供作为宿主的Host对象。
小结:由于在开发中一直使用Owin框架和以“系统服务”模式运行的程序,所以对服务承载系统并不陌生。


6.管道
6.1.概念:与管道相关的知识和概念主要分为中间件委托链、web服务器和承载服务,它们一起构成和使用管道。
6.1.1.中间件委托链
6.1.1.1.HttpContext:它表示针对当前请求的上下文。对于由一个服务器和多个中间件构成的管道来说,面向传输层的服务器负责请求的监听、接收和最终的响应,当它接收到客户端发送的请求后,需要将请求分发给后续中间件进行处理。对于某个中间件来说,完成自身的请求处理任务之后,在大部分情况下需要将请求分发给后续的中间件。请求在服务器与中间件之间,以及在中间件之间的分发是通过共享上下文的方式实现的。因此它最核心的作用就是负责请求和响应信息的管理与传递。
6.1.1.2.Middleware:在请求处理管道里,中间件是以类型为Func<RequestDelegate, RequestDelegate>的委托对象表示,即中间件的输入与输出都是一个RequestDelegate对象。对于管道中的某个中间件来说,后续中间件组成的管道体现为一个RequestDelegate对象,由于当前中间件在完成了自身的请求处理任务之后,往往需要将请求分发给后续中间件进行处理,所以它需要将后续中间件构成的RequestDelegate对象作为输入。特别说明:这里可能会有疑问,要理解先构建管道,之后再使用管道;而在中间件管道构建时将中间件集合反序,这里可以理解为用大盒子装小盒子,最后一个添加的中间件放在最小的盒子里面,第一个添加的中间件放在最大的盒子里面,有点像洋葱哈。
6.1.1.3.Middleware构建:表示中间件的Func<RequestDelegate, RequestDelegate>对象向表示请求处理器的RequestDelegate对象之间的转换是通过IApplicationBuilder对象来完成的。它的Use方法用来注册中间件,而Build方法则将所有的中间件按照注册的顺序组装成一个RequestDelegate对象。其实整个请求处理管道可以用表达式表示:Pipeline = Server + Middlewares(RequestDelegate)。
6.1.2.服务器
6.1.2.1.定义:它在管道中的职责非常明确,负责HTTP请求的监听、接收和最终的响应。具体来说,启动后的服务器会绑定到指定的端口进行请求监听。一旦有请求抵达,它会根据该请求创建代表请求上下文的HttpContext对象,并将该上下文分发给注册的中间件进行处理。当中间件管道完成了针对请求的处理之后,它会将最终生成的响应回复给客户端。
6.1.2.2.适配:面向应用层的HttpContext对象是对请求和响应的抽象与封装,但是请求最初是由面向传输层的服务器接收的,最终的响应也会由服务器回复给客户端。所以对于不同的服务器(IIS或Nginx等)应该解决其适配问题,解决方案就是添加抽象层:特性(Feature),最终开发的ASP.NET Core应用可以自由的选择不同类型的服务器部署。
6.1.3.承载服务:通过服务器和中间件构建好了管道,现在就需要使用管道,这就要通过第五章节介绍的服务承载系统实现。
6.2.HttpContext
6.2.1.请求(HttpRequest):具体功能可以查看HttpRequest抽象类的定义
6.2.2.响应(HttpResponse):具体功能可以查看HttpResponse抽象类的定义
6.2.3.特性(Feature)
6.2.3.1.定义:它是用来解决不同服务器适配问题。ASP.NET Core框架为抽象的HttpContext定义了一系列标准的特性接口来对请求上下文的各个方面进行描述。在一系列标准的接口中,最核心的是用来描述请求的IHttpRequestFeature接口和描述响应的IHttpResponseFeature接口。
6.2.3.2.创建HttpContext:对于具体的服务器来说,它需要提供这些特性接口的实现,并在接收到请求之后利用自行实现的特性来创建HttpContext上下文。在ASP.NET Core框架中,由服务器提供的特性集合通过IFeatureCollection接口表示,IFeatureCollection对象本质上就是一个Key和Value类型分别为Type与Object的字典。
6.2.3.3.使用HttpContext:不论是组成管道的中间件还是建立在管道上的应用,在默认情况下都利用DefaultHttpContext对象来获取当前请求的相关信息,并利用这个对象完成针对请求的响应。但是DefaultHttpContext对象在这个过程中只是一个“代理”,针对它的调用最终都需要转发给由具体服务器创建的那个原始上下文。
6.2.4.其他:除了上述主要三个核心对象,HttpContext上下文对象还包括其他信息
6.2.4.1.ClaimsPrincipal:表示当前请求的用户
6.2.4.2.ConnectionInfo:描述当前HTTP连接的信息
6.2.4.3.WebSocketManager:控制WebSocket的信息
6.2.4.4.ISession:控制当前会话的信息(一般不用了)
6.2.4.5.TraceIdentifier:获取或设置调试追踪的ID
6.2.4.6.Items集合:保存了与整个管道共享的当前上下文相关的数据
6.2.5.HttpContext应用
6.2.5.1.获取HttpContext:如果第三方组件需要获取表示当前请求上下文的HttpContext对象,就可以通过注入IHttpContextAccessor服务来实现。针对IHttpContextAccessor/HttpContextAccessor的服务注册可以通过如下所示的AddHttpContextAccessor扩展方法来完成。
6.2.5.2.创建HttpContext:管道在开始处理请求前对HttpContext上下文的创建,以及请求处理完成后对它的回收释放都是通过IHttpContextFactory对象完成的。
6.3.IServer
6.3.1.服务器(IServer)
6.3.1.1.定义:它是整个请求处理管道的“龙头”,所以启动和关闭应用的最终目的是启动和关闭服务器。ASP.NET Core框架中的服务器通过IServer接口来表示,该接口具有如下所示的3个成员,其中由服务器提供的特性就保存在其Features属性表示的IFeatureCollection集合中。IServer接口的StartAsync<TContext>方法与StopAsync方法分别用来启动和关闭服务器。
6.3.1.2.监听地址:服务器在开始监听请求之前总是绑定一个或者多个监听地址,这个地址是应用程序从外部指定的。监听地址会封装成一个特性,并且在服务器启动之前被添加到它的特性集合中。这个承载了监听地址列表的特性通过如下所示的IServerAddressesFeature接口来表示,该接口除了有一个表示地址列表的Addresses属性,还有一个布尔类型的PreferHostingUrls属性,该属性表示如果监听地址同时设置到承载系统配置和服务器上,是否优先考虑使用前者。
6.3.1.3.分发请求:服务器将用来处理由它接收请求的处理器会被视为一个通过IHttpApplication<TContext>接口表示的应用,所以可以将ASP.NET Core的请求处理管道视为IServer对象和IHttpApplication<TContext>对象的组合。
6.3.2.承载应用(HostingApplication)
6.3.2.1.定义:它类型作为IHttpApplication<TContext>接口的默认实现,它使用一个内嵌的Context类型来表示处理请求的上下文。一个Context对象是对一个HttpContext对象的封装,同时承载了一些与诊断相关的信息。
6.3.2.2.日志:它对象会在开始和完成请求处理,以及在请求过程中出现异常时发出一些诊断日志事件。
6.3.3.请求日志(RequestLog)
6.3.3.1.介绍:很多人可能对ASP.NET Core框架自身记录的诊断日志并不关心,其实很多时候这些日志对纠错排错和性能监控提供了很有用的信息。例如,假设需要创建一个APM(Application Performance Management)来监控ASP.NET Core处理请求的性能及出现的异常,那么我们完全可以将HostingApplication对象记录的日志作为收集的原始数据。
6.3.3.2.种类:HostingApplication对象有3中不同的诊断日志形式,包括基于DiagnosticSource和EventSource的诊断日志以及基于 .NET Core日志系统的日志。
6.3.3.3.ILogger日志
    a)通过注册对应ILoggerProvider对象的方式将日志内容写入对应的输出渠道
    b)通过ConfigureLogging方法注册一个ConsoleLoggerProvider对象,并开启针对日志范围的支持
    c)调用IApplicationBuilder接口的Run扩展方法注册了一个中间件,就可以使用了
    d)HostingApplication对象利用ILogger记录的日志中并不包含应用的异常信息
6.3.3.4.DiagnosticSource诊断日志
    a)通过注册诊断监听器来收集诊断信息,需要预先知道诊断日志事件的名称和内容荷载的数据结构。通过查看HostingApplication类型的源代码,我们会发现它针对“开始请求(BeginRequest)”、“结束请求(EndRequest)”和“未处理异常(UnhandledException)”这3类诊断日志事件对应的名称。
    b)定义诊断其监听器DiagnosticCollector,针对上述3类诊断事件,在DiagnosticCollector类型中定义了3个对应的方法,各个方法通过标注的DiagnosticNameAttribute特性设置了对应的诊断事件。
    c)使用Configure方法注入DiagnosticListener服务,并调用它的SubscribeWithAdapter扩展方法将上述DiagnosticCollector对象注册为诊断日志的订阅者。
6.3.3.5.EventSource事件日志
    a)HostingApplication对象针对每个请求的处理过程中还会利用EventSource对象发出相应的日志事件。它主要有5个日志事件:启动应用程序(HostStart)、开始处理请求(RequestStart)、请求处理结束(RequestStop)、未处理异常(UnhandledException)、关闭应用程序(HostStop)。
    b)利用创建的EventListener对象来监听上述5个日志事件,定义了派生于抽象类EventListener的DiagnosticCollector。
    c)通过注册其EventSourceCreated事件开启了针对目标名称为Microsoft.AspNetCore.Hosting的EventSource的监听。
    d)在注册的EventWritten事件中,我们将监听到的事件名称的负载内容输出到控制台上。
6.4.IMiddleware
6.4.1.IApplicationBuilder
6.4.1.1.定义:它是ASP.NET Core框架中的一个核心对象,我们将中间件注册在它上面,并且最终利用它来创建代表中间件委托链的RequestDelegate对象。
6.4.1.2.属性:ApplicationServices属性代表针对当前应用程序的依赖注入容器,ServerFeatures属性则返回服务器提供的特性集合,Properties属性返回的字典则代表一个可以用来存放任意属性的容器。
6.4.1.3.方法:Use方法用来注册中间件,Build方法用来构建中间件委托链的RequestDelegate对象,New方法创建一个新的IApplicationBuilder对象(除了中间件不同,它们具有相同的状态)。
6.4.1.4.IApplicationBuilderFactory:它会根据提供的特性集合创建相应的IApplicationBuilder对象。
6.4.2.中间件类型
6.4.2.1.弱类型中间件:它只能被注册为生命周期Singleton类型服务
    a)定义:它是按照预定义的约定规则来定义中间件类型,没有直接实现IMiddleware接口。
    b)约定1:中间件类型需要有一个有效的公共实例构造函数,该构造函数必须包含一个RequestDelegate类型的参数,当前中间件通过执行这个委托对象将请求分发给后续中间件进行处理。这个构造函数不仅可以包含任意其他参数,对参数RequestDelegate出现的位置也不做任何约束。
    c)约定2:针对请求的处理实现在返回类型为Task的Invoke方法或者InvokeAsync方法中,该方法的第一个参数表示当前请求对应的HttpContext上下文,对于后续的参数,虽然约定并未对此做限制,但是由于这些参数最终是由依赖注入框架提供的,所以相应的服务注册必须存在。
6.4.2.2.强类型中间件:直接实现IMiddleware接口,它可以注册为任意生命周期类型服务。
6.4.3.注册中间件:它有3种注册方式
6.4.3.1.调用IWebHostBuilder的Configure方法。
6.4.3.2.调用注册Startup类型的Configure方法。
6.4.3.3.利用注册的IStartupFilter对象。
6.5.启动流程
6.5.1.GenericWebXXXX
6.5.1.1.请求处理管道是由一个IServer对象和IHttpApplication对象构成的,在默认情况下,IHttpApplication是一个HostingApplication对象。一个HostingApplication对象由指定的RequestDelegate对象来完成所有的请求处理工作,而后者代表所有中间件按照注册的顺序串联而成的委托链。所有的这一切都被GenericWebHostService整合在一起。
6.5.1.2.GenericWebHostBuilder是基于IHostedService接口的实现。
6.5.1.3.承载体系
    a)Web主机(过时):基于IWebHost/IWebHostBuilder定义,WebHostBuilder是对IWebHostBuilder接口的默认实现
    b)通用主机(主流):基于IHost/IHostBuilder定义,GenericWebHostBuilder是对IWebHostBuilder接口的默认实现
6.5.2.StartAsync:在GenericWebHostService类型的StartAsync方法中用来启动应用程序的流程划分为如下4个步骤
    a)设置监听地址:服务器的监听地址是通过IServerAddressesFeature接口表示的特性来承载的,所以需要将配置提供的监听地址列表和相关的PreferHostingUrls选项(表示是否优先使用承载系统提供地址)转移到该特性中。
    b)构建中间件管道:通过调用IWebHostBuilder对象和注册的Startup类型的Configure方法针对中间件的注册会转换成一个Action<IApplicationBuilder>对象,并复制给配置选项GenericWebHostServiceOptions的ConfigureApplication属性。GenericWebHostService承载服务会利用注册的IApplicationBuilderFactory工厂创建出对应的IApplicationBuilder对象,并将该对象作为参数调用这个Action<IApplicationBuilder>对象就能将注册的中间件转移到IApplicationBuilder对象上。但在此之前,注册IStartupFilter对象的Configure方法会优先被调用,IStartupFilter对象针对前置中间件的注册就体现在这里。代表注册中间件管道的RequestDelegate对象最终通过调用IApplicationBuilder对象的Build方法返回。
    c)创建HostingApplication对象:在得到代表中间件管道的RequestDelegate之后,GenericWebHostService对象进一步利用它创建出HostingApplication对象,该对象对于服务器来说就是用来处理由它接收请求的应用程序。
    d)启动服务器:将创建出的HostingApplication对象作为参数调用作为服务器的IServer对象的StartAsync方法后,服务器随之被启动。此后,服务器绑定到指定的地址监听抵达的请求,并为接收的请求创建出对应的HttpContext上下文,后续中间件将在这个上下文中完成各自对请求的处理任务。请求处理结束之后,生成的响应最终通过服务器回复给客户端。
6.5.3.StopAsync:调用GenericWebHostService类型的StopAsync方法关闭服务器。
6.6.初始化
6.6.1.Startup
6.6.1.1.简介:Startup类承担应用的启动任务,所以按照约定,起名为Startup,不过你可以修改为任意类名(强烈建议类名为Startup)。当然本身是有IStartup接口的定义。
6.6.1.2.功能:主要包含两个方法,一是ConfigureServices方法用于注册服务,另一是Configure方法用于注册中间件。
6.6.2.IStartupFilter
6.6.2.1.简介:除了在Startup.Configure方法中注册中间件,还可以通过注册IStartupFilter服务来达到相同的目的。一个应用程序可以注册多个IStartupFilter服务,它们会按照注册的顺序组成一个链表。IStartupFilter接口具有如下所示的唯一方法Configure,中间件的注册体现在它返回的Action<IApplicationBuilder>对象上。
6.6.2.2.特点:虽然注册中间件是IStartup对象和IStartupFilter对象的核心功能,但是两者之间还是不尽相同的,它们之间的差异在于:IStartupFilter对象的Configure方法会在IStartup对象的Configure方法之前执行。正因为如此,如果需要将注册的中间件前置或者后置,就需要利用IStartupFilter对象来注册它们。
6.6.3.IHostingStartup
6.6.3.1.简介:除了通过注册Startup类型来初始化应用程序,我们还可以通过注册一个或者多个IHostingStartup服务达到类似的目的。由于IHostingStartup服务可以通过第三方程序集来提供,如果第三方框架、类库或者工具需要在应用启动时做相应的初始化工作,就可以将这些工作实现在注册的IHostingStart服务中。它使多项目、模块化/插件化、无侵入式开发得以实现。
6.6.3.2.定义:IHostingStartup接口位于Microsoft.AspNetCore.Hosting包中,且只定义了一个唯一的Configure方法,该方法可以利用输入参数得到当前使用的IWebHostBuilder对象。
6.6.3.3.注册:IHostingStartup服务是通过HostingStartupAttribute特性来注册的,在开发的程序集项目中随意定义一个类,然后使用此特性注册即可。
6.6.3.4.配置:如果希望某个程序集提供的IHostingStartup服务类型能够真正应用到当前程序中,需要采用配置的形式对程序集进行注册。
    a)注册IHostingStartup程序集的配置项名称为hostingStartupAssemblies,对应静态类型WebHostDefaults的只读字段HostingStartupAssembliesKey。通过配置形式注册的程序集名称以分号“;”进行分隔。
    b)如果不希望第三方程序集对当前应用程序进行干预,我们可以通过配置项preventHostingStartup关闭这一特性,该配置项的名称对应WebHostDefaults的PreventHostingStartupKey属性。
    c)WebHostDefaults还通过HostingStartupExcludeAssembliesKey属性定义了另一个配置项,其名称为hostingStartupExcludeAssemblies,用于设置需要被排除的程序集列表。
    d)IHostingStartup相关的配置只有通过环境变量和调用IWebHostBuilder接口的UseSetting方法进行设置才有效,所以虽然采用命令行参数提供原始配置,但是必须调用UseSetting方法将它们应用到IWebHostBuilder对象上。
    e)environmentVariables: {  ASPNETCORE_HOSTINGSTARTUPASSEMBLIES: HostStartupLib;HostStartupLib2 }
小结:管道这部分内容相对来说是新鲜的,其中还将原先在IIS上配置的一些功能增加进来了,毕竟可以脱离IIS独立运行,而且还要支持其他Web服务器。之所以管道新鲜,是因为这部分内容既有一定的深度和广度,也有对一些知识概念的汇总和串联,不过话要说回来也就是那回事儿,该用的技术早就玩过了,就是换了身皮、挪了挪地方。到此处我大概明白为什么有些小伙伴改道其他编程语言,因为ASP.NET Core中出现的知识和概念太多了,比如依赖注入、中间件、管道、Options模型、设计模式(创建者\工厂\单例\适配器等)、服务承载系统、文件系统等等,这些知识和概念在ASP.NET时期属于存在但不可见,如今现身把一些小伙伴惊吓到了,就错误的认为使用ASP.NET Core等于重新学习一门编程技术,如此还不如改道学其他编程语言呢,对此我不做评论,大家都有自己的选择。


7.中间件
7.1.静态文件中间件
7.1.1.静态文件中间件:主要围绕StaticFileMiddleware中间件的使用和分析
7.1.2.目录浏览中间件:主要围绕DirectoryBrowserMiddleware中间件的使用和分析
7.1.3.默认文件中间件:主要围绕DefaultFilesMiddleware中间件的使用和分析
7.2.路由中间件
7.2.1.路由原理:当应用接收到请求并创建HttpContext上下文之后,EndpointRoutingMiddleware中间件会根据请求的URL及其他相关信息从注册的终结点中选择匹配度最高的那个。之后被选择的终结点会以一个特性(Feature)的形式附加到当前HttpContext上下文中,EndpointMiddleware中间件执行终结点处理当前请求。
7.2.2.终结点路由:一个Web应用本质上体现为一组终结点的集合。终结点则体现为一个暴露在网络中可供外界采用HTTP协议调用的服务,路由的作用就是建立一个请求URL模式与对应终结点之间的映射关系。借助这个映射关系,客户端可以采用模式匹配的URL来调用对应的终结点。
7.2.3.路由规则的定义与设置。
7.3.错误处理中间件
7.3.1.开发者异常页面:主要围绕DeveloperExceptionPageMiddleware中间件的使用和分析
7.3.2.异常处理器:主要围绕ExceptionHandlerMiddleware中间件的使用和分析
7.3.3.响应状态码错误页面:主要围绕StatusCodePagesMiddleware中间件的使用和分析
小结:对于中间件这个概念,我认为它只是不同时期的名称不同而已,webform叫HttpModule/HttpHandle处理器,后来叫IFilter过滤器,现在叫IMiddleware中间件。其实以前的那些概念都还在,并没有完全消失。


8.部署运维
8.1.Kestrel
8.1.1.开发使用
8.1.1.1.UseKestrel扩展方法
    a)IWebHostBuilder接口定义了三个UseKestrel扩展方法重载,它们将会完成完成KestrelServer的注册并对KestrelServerOptions配置选项作相应设置。
    b)注册到KestrelServer上的终结点体现为Endpoint对象。Endpoint是对网络地址的抽象,在大部分下体现为“IP地址+端口”或者“域名+端口”,对应的类型分别为IPEndPoint和DnsEndPoint。
    c)终结点注册利用ListenOptions配置选项来描述。该类型实现的IConnectionBuilder和IMultiplexedConnectionBuilder接口涉及针对连接的构建。注册的终结点体现为该配置选项的EndPoint属性,如果是一个IPEndPoint对象,该对象也会体现在IPEndPoint属性上。
    d)同一个终结点可以同时支持HTTP 1.x、HTTP 2 和HTTP 3三种协议,具体设置体现在Protocols属性上,该属性返回HttpProtocols枚举。由于枚举项Http3和Http1AndHttp2AndHttp3标注了RequiresPreviewFeaturesAttribute特性,如果需要采用HTTP 3协议,项目文件中必须添加“<EnablePreviewFeatures>true</EnablePreviewFeatures>”属性。如果HTTP3终结点同时支持HTTP 1.X和HTTP 2,针对HTTP 1.X和HTTP 2的请求的响应一般会添加一个alt-svc (Alternative Service)报头指示可以升级到HTTP 3,我们可以设置DisableAltSvcHeader属性关闭此特性。该属性默认值为Http1AndHttp2。
8.1.1.2.两种终结点的取舍
    a)监听地址不仅可以添加到WebApplication对象的Urls属性中,WebApplication类型用来启动应用的RunAsync和Run方法也提供了可缺省的参数url来指定监听地址。不过这三种凡是提供的监听地址都会被添加到IServerAddressesFeature特性的Addresses属性中。
    b)如果KestrelServerOptions配置选项不能提供注册的终结点,那么KestrelServer就会使用IServerAddressesFeature特性提供的地址来创建对应的终结点,否则就会根据它的PreferHostingUrls属性来进行取舍。
    c)如果IServerAddressesFeature特性的PreferHostingUrls属性返回True,它提供的地址会被选择,否则就使用直接注册到KestrelServerOptions配置选项的终结点。
    d)如果服务器的特性集合提供的IServerAddressesFeature特性包含监听地址,以配置方式设置的监听地址和针对PreferHostingUrls的设置将会被忽略,这一个特性体现在GenericWebHostService的StartAsync方法中。该方法会从服务器中提取IServerAddressesFeature特性,只有该特性不能提供监听地址的情况下,利用配置注册的监听地址和针对PreferHostingUrls的设置才会应用到该特性中。
8.1.1.3.终结点配置
    a)KestrelServerOptions承载的很多设置都可以利用配置来提供。由于该配置选项类型的定义与配置的结构存在差异, KestrelServerOptions配置选项无法直接使用对应的IConfiguration对象进行绑定,所以KestrelServerOptions类型定义三个Configure方法。带参数的两个方法提供了承载配置内容的IConfiguration对象,最后一个重载还提供了reloadOnChange参数来决定是否自动加载更新后的配置。无参数方法提供的其实是一个空的IConfiguration对象。
    b)三个Configure方法都返回KestrelConfigurationLoader对象,后者是对当前KestrelServerOptions配置选项和指定IConfiguration对象的封装。KestrelConfigurationLoader的Load方法会读取配置的内容并将其应用到KestrelServerOptions配置选项上。
    c)ASP.NET Core应用在启动时会调用IHostBuilder接口ConfigureWebHostDefaults扩展方法进行初始化设置,该方法会从当前配置中提取出“Kestrel”配置节,并将其作为参数调用Configure方法将配置内容应用到KestrelServerOptions配置选项上。由于reloadOnChange参数被设置成了True,所以更新后的配置会自动被重新加载。
    d)KestrelServerOptions绝大部分配置选项都可以定义在配置文件中,具体的配置定义方法可以参阅官方文档。
8.1.1.4.针对HTTPS的设置
    a)HTTPS(SSL/TLS)终结点的配置由HttpsConnectionAdapterOptions负责,KestrelServerOptions的ConfigureHttpsDefaults方法为所有HTTPS终结点提供了默认的设置。
    b)表示服务端证书的X509Certificate2对象可以直接设置到ServerCertificate属性上,也可以在ServerCertificateSelector属性上设置一个根据当前连结动态选择证书的委托。SslProtocols属性用来设置采用的协议(SSL或者TLS),对应的类型为SslProtocols枚举。HandshakeTimeout属性用来设置TLS/SSL“握手”的超时时间,默认为10秒。
    c)HTTPS主要解决的是服务端的认证和传输安全问题,所以服务端的认证信息需要在前期“协商”阶段利用建立的安全通道传递给客户端,具体的认证信息是SslServerAuthenticationOptions配置选项格式化后的结果。HttpsConnectionAdapterOptions的OnAuthenticate属性提供的委托对这个配置选项进行设置,所以绝大部分HTTPS相关的设置都可以利用该属性来完成。
    d)HTTPS不仅仅能够帮助客户端来验证服务端的身份,还能帮助服务端来对客户端身份进行验证。服务端验证利用服务端证书来完成,与之类似,服务端要识别客户端的身份,同样需要客户端提供证书。我们可以利用HttpsConnectionAdapterOptions的ClientCertificateMode属性来决定是否要求客户端提供证书,该属性类型为ClientCertificateMode枚举。针对客户端认证的验证可以利用ClientCertificateValidation属性设置的委托来完成。
    e)由权威机构(Certificate Authority)颁发的证书可能会由于某种原因被撤销,有两种途径来确定某张证书是否处于被撤销的状态:证书颁发机构可以采用标准的OCSP(Online Certificate Status Protocol)协议提供用于确定证书状态的API,也可以直接提供一份撤销的证书清单(CRL:Certificate Revocation List)。HttpsConnectionAdapterOptions的CheckCertificateRevocation属性用来决定是否需要对证书的撤销状态进行验证。如果不需要对客户端证书作任何验证,我们可以调用HttpsConnectionAdapterOptions的AllowAnyClientCertificate方法。
    f)将某个终结点注册到KestrelServer上并生成对应ListenOptions配置选项后,可以调用后者的UseHttps扩展方法完成针对HTTPS的设置。对于证书的设置,可以直接指定一个X509Certificate2对象,也可以指定证书文件的路径(一般还需要提供读取证书的密码),还可以指定证书的存储(Certificate Store)。
    g)除了通过上述方法为注册的终结点提供HTTPS相关的设置外,这些设置也可以放在终结点的配置中。
      {
        Kestrel: {
          Endpoints: {
            MyHttpsEndpoint: {
              Url: https://localhost:5001,
              ClientCertificateMode: AllowCertificate,
              Certificate: {
                Path: c:\\certificates\\foobar.pfx>,
                Password: password
              }
            }
          }
        }
      }
8.1.1.5.限制约束
    a)为了确保KestrelServer稳定可靠地运行,需要根据需要为它设置相应的限制和约束,这些设置体现在KestrelServerOptions配置选项Limits属性返回的KestrelServerLimits对象上。
    b)KestrelServerLimits利用其丰富的属性对连接、请求和响应进行了相应的限制。
        MaxConcurrentConnections:最大并发连接。如果设置为Null(默认值),意味着不作限制。
        MaxConcurrentUpgradedConnections:可升级连接(比如从HTTP升级到WebSocket)的最大并发数。如果设置为Null(默认值),意味着不作限制。
        KeepAliveTimeout:连接保持活动状态的超时时间,默认值为130秒。
        MaxRequestHeaderCount:请求携带的最大报头数量,默认值为100。
        MaxRequestBufferSize:请求缓冲区最大容量,默认值为1,048,576字节(1M)。
        MaxRequestHeadersTotalSize:请求携带报头总字节数,默认值为 32,768字节(32K)。
        MaxRequestLineSize:对于HTTP 1.X来说就是请求的首行(Request Line)最大字节数。对于HTTP 2/3来说就是 :method, :scheme, :authority, and :path这些报头的总字节数。默认值为8,192 字节(8K)。
        MaxRequestBodySize:请求主体最大字节数,默认值为30,000,000 字节(约28.6M)。如果设置为Null,意味着不作限制。
        RequestHeadersTimeout:接收请求报头的超时时间,默认为30秒。
        MinRequestBodyDataRate:请求主体内容最低传输率。
        MaxResponseBufferSize:响应缓冲区最大容量,默认值为65,536(1M)。
        MinResponseDataRate:响应最低传输率。
    c)HTTP 1.x:HTTP 1.X建立在TCP之上,客户端和服务端之间的交互依赖预先创建的TCP连接。虽然HTTP 1.1引入的流水线技术允许客户端可以随时向服务端发送请求,而无需等待接收到上一个请求的响应,但是响应依然只能按照请求的接收顺序返回的。真正意义上的“并发”请求只能利用多个连接来完成,但是针对同一个域名支持的TCP连接的数量又是有限的。这个问题在HTTP 2得到了一定程度的解决。
    d)HTTP 2:与采用文本编码的HTTP 1.X相比, HTTP 2采用更加高效的二进制编码。帧(Frame)成为了基本通信单元,单个请求和响应可以分解成多个帧进行发送。客户端和服务端之间额消息交换在一个支持双向通信的信道(Channel)中完成,该信道被称为“流(Stream)”。每一个流具有一个唯一标识,同一个TCP连接可以承载成百上千的流。每个帧携带着所属流的标识,所以它可以随时被“乱序”发送,接收端可以利用流的标识进行重组,所以HTTP 2在同一个TCP连接上实现了“多路复用”。使用同一个连接发送的请求和响应都存在很多重复的报头,为了减少报头内容占据的带宽,HTTP 2会采用一种名为HPACK的压缩算法对报头文本进行编码。HPACK会在发送和接收端维护一个索引表来存储编码的文本,报头内容在发送前会被替换成在该表的索引,接收端这利用此索引在本地压缩表中找到原始的内容。
    e)HTTP 2配置:HTTP 2相关限制和约束的设置体现在KestrelServerLimits的Http2属性上,该属性返回如上所示的Http2Limits对象。
        MaxStreamsPerConnection:连接能够承载的流数量,默认值为100。
        HeaderTableSize:HPACK报头压缩表的容量,默认值为4096。
        MaxFrameSize:帧的最大字节数,有效值在[214~224 – 1]区间范围内,默认值为214(16384)。
        MaxRequestHeaderFieldSize:最大请求报头(含报头名称)的最大字节数,默认值为214(16384)。
        InitialConnectionWindowSize:连接的初始化请求主体缓存区的大小,有效值在[65535~231]区间范围内,默认为131072。
        InitialStreamWindowSize:流的初始化请求主体缓存区的大小,有效值在[65535~231]区间范围内,默认为98304。
        KeepAlivePingDelay:如果服务端在该属性设定的时间跨度内没有接收到来自客户端的有效帧,它会主动发送Ping请求确定客户端的是否保持活动状态,默认值为1秒。
        KeepAlivePingTimeout:发送Ping请求的超时时间,如果客户端在该时限内一直处于为活动状态,当前连接将被关闭,默认值为20秒。
    f)HTTP 3:由于HTTP 2的多路复用是在同一个TCP连接上实现的,这样的实现并不“纯粹”,因为它不可能解决由于TCP的“拥塞控制”机制导致的“队头阻塞(Header-Of-Line Blocking)”问题。如果希望在得到并发支持的前提下还能在低延时上有更好的作为,就不得不抛弃TCP。目前被正式确定为HTTP 3的QUIC(Quick UDP Internet Connection)就将TCP替换成了UDP。如果KestrelServer支持HTTP 3,我们可以利用KestrelServerLimits的Http3属性返回的Http3Limits对象都限制约束进行针对性设置。Http3Limits只包含如下这个表示最大请求报头字节数的MaxRequestHeaderFieldSize属性,它的默认值为16384。
8.1.1.6.其他设置
    a)除了注册的终结点和基于通信的限制约束,KestrelServerOptions配置选项还利用如下的属性承载着其他的设置。
    b)KestrelServerOptions类型中的其他设置属性
        AddServerHeader:是否会在回复的响应中自动添加“Server: Kestrel”报头,默认值为True。
        AllowResponseHeaderCompression:是否允许对响应报头进行HPACK压缩,默认值为True。
        AllowSynchronousIO:是否允许对请求和响应进行同步IO操作,默认值为False,意味这个默认情况下以同步方式读取请求和写入响应都会抛出异常。
        AllowAlternateSchemes:是否允许为“:scheme”字段(针对HTTP 2和HTTP 3)提供一个与当前传输不匹配的值(“http”或者“https”),默认值为False。如果将这个属性设置为True,意味着HttpRequest.Scheme属性可能与采用的传输类型不匹配。
        DisableStringReuse:创建的字符串是否可以在多个请求中复用。
        RequestHeaderEncodingSelector:用于设置某个请求报头采用的编码方式,默认为Utf8Encoding。
        ResponseHeaderEncodingSelector:用于设置某个响应报头采用的编码方式,默认为ASCIIEncoding。
8.1.2.设计原理:当KestrelServer启动的时候,注册的每个终结点将转换成对应的“连接监听器”,后者在监听到初始请求时会创建“连接”,请求的接收和响应的回复都在这个连接中完成。
8.1.2.1.连接上下文(ConnectionContext )
    a)监听器创建的连接是一个抽象的概念,可以将其视为客户端和服务端完成消息交换而构建的“上下文”,该上下文通过ConnectionContext类型表示。ConnectionContext派生于抽象基类BaseConnectionContext,后者实现了IAsyncDisposable接口。
    b)每个连接具有一个通过ConnectionId属性表示的ID,它的LocalEndPoint和RemoteEndPoint属性返回本地(服务端)和远程(客户端)终结点。
    c)服务器提供的特性集合体现在它的Features属性上,另一个Items提供了一个存放任意属性的字典。ConnectionClosed属性提供的CancellationToken可以用来接收连接关闭的通知。Abort方法可以中断当前连接,这两个方法在ConnectionContext被重写。    ConnectionContext类型的Transport属性提供的IDuplexPipe对象是用来对请求和响应进行读写的双向管道。
    d)如果采用HTTP 1.X和HTTP 2协议,KestrelServer会采用TCP套接字(Socket)进行通信,对应的连接体现为一个SocketConnection对象。如果采用的是HTTP 3,会采用基于UDP的QUIC协议进行通信,对应的连接体现为一个QuicStreamContext对象。
8.1.2.2.连接监听器(IConnectionListener )
    a)KestrelServer同时支持三个版本的HTTP协议,HTTP 1.X和HTTP 2建立在TCP协议之上,针对这样的终结点会转换成通过IConnectionListener接口表示的监听器。它的EndPoint属性表示监听器绑定的终结点,当AcceptAsync方法被调用时,监听器便开始了网络监听工作。当来自某个客户端端的初始请求抵达后,它会将创建代表连接的ConnectionContext上下文创建出来。另一个UnbindAsync方法用来解除终结点绑定,并停止监听。
    b)QUIC利用传输层的UDP协议实现了真正意义上的“多路复用”,所以它将对应的连接监听器接口命名为IMultiplexedConnectionListener。它的AcceptAsync方法创建的是代表多路复用连接的MultiplexedConnectionContext对象,后者的AcceptAsync会将ConnectionContext上下文创建出来。QuicConnectionContext 类型是对MultiplexedConnectionContext的具体实现,它的AcceptAsync方法创建的就是上述的QuicStreamContext对象,该类型派生于抽象类TransportMultiplexedConnection。
    c)KestrelServer使用的连接监听器均由对应的工厂来构建。IConnectionListenerFactory接口代表用来构建IConnectionListener监听器的工厂,IMultiplexedConnectionListenerFactory工厂则用来构建IMultiplexedConnectionListener监听器。
8.1.2.3.设计流程:KestrelServer启动时会根据每个终结点支持的HTTP协议利用IConnectionListenerFactory或者IMultiplexedConnectionListenerFactory工厂来创建代表连接监听器的IConnectionListener或者IMultiplexedConnectionListener对象。IConnectionListener监听器会直接将代表连接的ConnectionContext上下文创建出来,IMultiplexedConnectionListener监听器创建的则是一个MultiplexedConnectionContext上下文,代表具体连接的ConnectionContext上下文会进一步由该对象进行创建。
8.2.IIS
8.2.1.ASP.NET CORE Core Module
    a)IIS其实也是按照管道的方式来处理请求的,但是IIS管道和ASP.NET CORE中间件管道有本质的不同。对于部署在IIS中的Web应用来说,从最初接收到请求到最终将响应发出去,这段处理流程被细分为一系列固定的步骤,每个都具有一个或者两个(前置+后置)对应的事件或者回调。利用自定义的Module注册相应的事件或回调在适当的时机接管请求,并按照自己希望的方式对它进行处理。
    b)IIS提供了一系列原生(Native)的Module,我们也可以使用任意.NET语言编写托管的Module,整合IIS和ASP.NET CORE 的这个ASP.NET CORE Core Module就是一个原生的Module。它利用注册的事件将请求从IIS管道中拦截下来,并转发给ASP.NET CORE管道进行处理。相应的安装包可以从https://dotnet.microsoft.com/permalink/dotnetcore-current-windows-runtime-bundle-installer下载。
8.2.2.In-Process部署模式:不使用Kestrel,直接使用IIS。
    a)ASP.NET CORE在IIS下有In-Process和Out-of-Process两种部署模式。In-Process模式下的ASP.NET CORE应用运行在IIS的工作进程w3wp.exe中(如果采用IIS Express,工作进程为iisexpress.exe)。ASP.NET CORE应用在这种模式下使用的服务器类型是IISHttpServer,上述的ASP.NET CORE Core Module会将原始的请求转发给这个服务器,并将后者生成响应转交给IIS服务器进行回复。In-Process是默认采用的部署模式。
    b)示例过程演示
      1)在IIS的默认站点(Defaut Web Site)创建一个名为WebApp的应用,并将映射的物理路径设置为“C:\App”
      2)创建一个空的ASP.NET CORE程序,并编写了将当前进程名称作为响应内容的演示程序
      3)在Visual Studio的解决方案视图右键选择该项目,在弹出的菜单中选择“发布(Publish)”选项,创建一个指向“C:\App”的Publish Profile,然后执行这个Profile完成发布工作
      4)应用部署好之后,在浏览器输入地址“http://localhost/webapp”访问部署好的应用,从输出结果可以看出ASP.NET CORE应用实际上就运行在IIS的工作进程中
    c)此时查看部署目录(“C:\App”),会发现生成的程序集和配置文件。应用既然部署在IIS中,那么具体的配置自然定义在web.config中。发现所有的请求(path=* verb=*)都被映射到“AspNetCoreModuleV2”这个Module上。至于这个Module如果启动ASP.NET CORE管道并与之交互,则由后面的<aspNetCore>配置节来控制,可以看到它将表示部署模式的hostingModel属性设置为“inprocess”。
    d)In-Process模式会注册IISHttpServer,对应的配置选项定义在IISServerOptions中。
    e)针对IISHttpServer的注册实现在IWebHostBuilder接口UseIIS扩展方法中。
8.2.3.Out-of-Process部署模式:使用Kestrel,IIS作为反向代理。
    a)Out-of -Process部署模式,采用KestrelServer的ASP.NET CORE应用运行在独立的dotnet.exe进程中。当IIS接受到针对目标应用的请求时,如果目标应用所在的进程并未启动,ASP.NET CORE Core Module还负责执行dotnet命令激活此进程,相当于充当了WAS(Windows Activation Service)的作用。当然IIS回收线程池时也会关闭该进程。
    b)在激活ASP.NET CORE承载进程之前,ASP.NET CORE Core Module会选择一个可用的端口号,该端口号和当前应用的路径(该路径将作用ASP.NET CORE应用的PathBase)被写入环境变量,对应的环境变量名称分别为“ASPNETCORE_PORT”和“ASPNETCORE_APPL_PATH”。
    c)以Out-of-Process模式部署的ASP.NET CORE应用只会接收IIS转发给它的请求,为了能够过滤其它来源的请求,ASP.NET CORE Core Module会生成一个Token并写入环境变量“ASPNETCORE_TOKEN”。后续转发的请求会利用一个报头“MS-ASPNETCORE-TOKEN”传递此Token,ASP.NET CORE应用会校验是否与之前生成的Token匹配。ASP.NET CORE Core Module还会利用环境变量传递其他一些设置。
    d)由于Out-of-Process部署模式涉及本地回环网络(Loopback)的访问,其性能自然不如In-Process部署模式。
8.2.4.<aspnetcore>配置
    a)不论是采用何种部署模式,相关的配置都定义在部署目录下的web.config配置文件,它提供的针对ASP.NET CORE Core Module的映射使我们能够将ASP.NET CORE应用部署在IIS中。在web.config中,与ASP.NET CORE应用部署相关的配置定义在<aspNetCore>配置节中。
    b)配置节点
      <aspNetCore
        processPath = dotnet
        arguments = .\App.dll
        stdoutLogEnabled = false
        stdoutLogFile = .\logs\stdout
        hostingModel = outofprocess
        forwardWindowsAuthToken = true
        processesPerApplication = 10
        rapidFailsPerMinute = 5
        requestTimeout = 00:02:00
        shutdownTimeLimit = 60
        startupRetryCount = 3
        startupTimeLimit = 60>
        <environmentVariables>
          <environmentVariable name = ASPNETCORE_ENVIRONMENT value = Development/>
        </environmentVariables>
        <handlerSettings>
          <handlerSetting name = stackSize value = 2097152 />
          <handlerSetting name = debugFile value = .\logs\aspnetcore-debug.log />
          <handlerSetting name = debugLevel value = FILE,TRACE />
        </handlerSettings>
      </aspNetCore>
    c)配置属性说明
      processPath:ASP.NET CORE应用启动命令所在路径,必需。
      arguments:ASP.NET CORE应用启动传入的参数,可选。
      stdoutLogEnabled:是否将stdout 和stderr输出到 stdoutLogFile属性指定的文件,默认为False。
      stdoutLogFile:作为stdout 和stderr输出的日志文件,默认为“ aspnetcore-stdout”。
      hostingModel:部署模式,“inprocess/InProcess”或者“outofprocess/OutOfProcess”(默认值)。
      forwardWindowsAuthToken:是否转发Windows认证令牌,默认为True。
      processesPerApplication:承载ASP.NET CORE应用的进程( processPath)数,默认为1。该配置对In-Process模式无效。
      rapidFailsPerMinute:ASP.NET CORE应用承载进程( processPath)每分钟允许崩溃的次数,默认为10,超过此数量将不再试图重新启动它。
      requestTimeout:请求处理超时时间,默认为2分钟。
      startupRetryCount:ASP.NET CORE应用承载进程启动重试次数,默认为2次。
      startupTimeLimit:ASP.NET CORE应用承载进程启动超时时间(单位为秒),默认为120秒。
      environmentVariables:设置环境变量。
      handlerSettings:为ASP.NET CORE Core Module提供额外的配置。
8.3.HTTP.SYS
8.3.1.说明
  a)如果只需要将ASP.NET CORE应用部署到Windows环境下,并且希望获得更好的性能,那么选择的服务器类型应该是HTTP.SYS。Windows环境下任何针对HTTP的网络监听器/服务器在性能上都无法与HTTP.SYS比肩。
  b)HTTP.SYS本质上就是一个HTTP/HTTPS监听器,它是Windows网络子系统的一部分,是一个在内核模式下运行的网络驱动。HTTP.SYS对应的驱动文件为“%WinDir\System32\drivers\http.sys”,不要小看这个只有1M多的文件,Windows系统针对HTTP的监听、接收、转发和响应大都依赖它。HTTP.SYS建立在Windows网络子系统针对TCPIP协议栈的驱动(TCPIP.SYS)之上,并为用户态运行的IIS提供基础的HTTP通信服务。
  c)由于HTTP.SYS是在操作系统内核态运行,所以它提供的性能优势是其他在用户态运行的同类产品无法比拟的。由于它自身提供响应缓存,所以在缓存命中的情况下根本不需要与用户态进程进行交互。它还提供了请求队列(Request Queue),如果请求的目标进程(比如IIS的工作进程)处于活动状态,它可以直接将请求分它给它,否则请求会暂存于队列中等待目标进程来提取,这样的工作模式既减少了内核态与用户态之间的上下文切换,也确保请求不会丢失。HTTP.SYS还提供连接管理,流量限制,诊断日志等功能,并提供针对Kerberos的Windows认证。
  d)由于HTTP.SYS是一个底层共享的网络驱动,它有效地解决了端口共享的问题。用户态进程会使用地址前缀(含端口号)“接入”HTTP.SYS,后者利用提供的地址前缀来转发请求,多个用户态进程只要保证提供的地址前缀不同就可以了,所以它们可以使用相同的端口号。端口共享使每个用户进程都可以使用标准的80/443端口。
8.3.2.MessagePump & UseHttpSys
  a)基于HTTP.SYS的服务器体现为MessagePump类型,它内部使用一个HttpSysListener对象采用注册的监听地址接入HTTP.SYS。MessagePump提供针对HTTP 1.X、HTTP 2以及HTTPS的支持。对于Windows Server 2022和Windows 11,还支持HTTP 3。
  b)IWebHostBuilder接口UseHttpSys扩展方法用来完成针对MessagePump的注册。
8.3.3.HttpSysOptions
  在调用UseHttpSys扩展方法注册基于HTTP.SYS的MessagePump服务器的时候,利用提供的Action<HttpSysOptions>委托对相关的配置选项进行设置。HttpSysOptions的UrlPrefixes属性返回注册的监听地址前缀,但是最终是否这种直接注册到服务器上的监听器地址,取决于IServerAddressesFeature特性的PreferHostingUrls属性,这一点与KestrelServer是一致的。
8.4.Ngnix
8.4.1.说明:使用Ngnix主要作为反向代理服务器,实际ASP.NET服务程序运行在自身的Kestrel服务器里。
8.4.2.步骤:
      a)安装dotNet运行环境,具体过程看官方教程:https://dotnet.microsoft.com/en-us/download
      b)部署ASP.NET应用程序,一般都是在windows系统环境编程,所以需要把发布好的程序上传到linux服务器指定的文件目录。
      c)检查是否能够运行:dotnet /xxxx/xxx/test.dll。不过现在还不能通过浏览器访问,需要配置web服务器进行转发。
      d)安装nginx
        命令:yum install nginx
        输入:systemctl start nginx 来启动nginx。
        输入:systemctl enable nginx
      e)配置防火墙
        命令:firewall-cmd --zone=public --add-port=80/tcp --permanent(开放80端口)
        命令:systemctl restart firewalld(重启防火墙以使配置即时生效)
      f)将nginx添加SELinux白名单(此步看情况吧,如果已经包含在白名单里就不用管了)
        yum install policycoreutils-python
        sudo cat /var/log/audit/audit.log | grep nginx | grep denied | audit2allow -M mynginx
        sudo semodule -i mynginx.pp
      g)nginx转发请求
        修改 /etc/nginx/conf.d/default.conf 文件。将文件内容替换为
        server {
          listen 80;
          location / {
            proxy_pass http://localhost:5000;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection keep-alive;
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
          }
        }
        执行:nginx –s reload 使其即时生效
      h)通过浏览器网址访问。
8.5.Supervisor
8.5.1.简介:supervisor是用Python开发的一个client/server服务,是Linux/Unix系统下的一个进程管理工具。可以很方便的监听、启动、停止、重启一个或多个进程。用supervisor管理的进程,当一个进程意外被杀死,supervisor监听到进程死后,会自动将它重启,很方便的做到进程自动恢复的功能,不再需要自己写shell脚本来控制。
8.5.2.安装:yum install supervisor,安装好后在/etc/会生成一个supervisord.conf文件及一个supervisord.d文件目录,supervisord.d目录用来存放用户自定义的进程配置。
8.5.3.配置
        01.[unix_http_server]
        02.file=/tmp/supervisor.sock ;UNIX socket 文件,supervisorctl 会使用
        03.;chmod=0700 ;socket文件的mode,默认是0700
        04.;chown=nobody:nogroup ;socket文件的owner,格式:uid:gid

        05.;[inet_http_server] ;HTTP服务器,提供web管理界面
        06.;port=127.0.0.1:9001 ;Web管理后台运行的IP和端口,如果开放到公网,需要注意安全性
        07.;username=user ;登录管理后台的用户名
        08.;password=123 ;登录管理后台的密码

        09.[supervisord]
        10.logfile=/tmp/supervisord.log ;日志文件,默认是 $CWD/supervisord.log
        11.logfile_maxbytes=50MB ;日志文件大小,超出会rotate,默认 50MB,如果设成0,表示不限制大小
        12.logfile_backups=10 ;日志文件保留备份数量默认10,设为0表示不备份
        13.loglevel=info ;日志级别,默认info,其它: debug,warn,trace
        14.pidfile=/tmp/supervisord.pid ;pid 文件
        15.nodaemon=false ;是否在前台启动,默认是false,即以 daemon 的方式启动
        16.minfds=1024 ;可以打开的文件描述符的最小值,默认 1024
        17.minprocs=200 ;可以打开的进程数的最小值,默认 200

        18.[supervisorctl]
        19.serverurl=unix:///tmp/supervisor.sock ;通过UNIX socket连接supervisord,路径与unix_http_server部分的file一致
        20.;serverurl=http://127.0.0.1:9001 ; 通过HTTP的方式连接supervisord

        21.[program:xx] ; [program:xx]是被管理的进程配置参数,xx是进程的名称
        22.command=/opt/test ; 程序启动命令
        23.autostart=true ; 在supervisord启动的时候也自动启动
        24.startsecs=10 ; 启动10秒后没有异常退出,就表示进程正常启动了,默认为1秒
        25.autorestart=true ; 程序退出后自动重启,可选值:[unexpected,true,false],默认为unexpected,表示进程意外杀死后才重启
        26.startretries=3 ; 启动失败自动重试次数,默认是3
        27.user=test ; 用哪个用户启动进程,默认是root
        28.priority=999 ; 进程启动优先级,默认999,值小的优先启动
        29.redirect_stderr=true ; 把stderr重定向到stdout,默认false
        30.stdout_logfile_maxbytes=20MB ; stdout 日志文件大小,默认50MB
        31.stdout_logfile_backups = 20 ; stdout 日志文件备份数,默认是10
        32.stdout_logfile=/opt/test/logs/catalina.out ; stdout 日志文件,需要注意当指定目录不存在时无法正常启动,所以需要手动创建目录(supervisord 会自动创建日志文件)
        33.stopasgroup=false ;默认为false,进程被杀死时,是否向这个进程组发送stop信号,包括子进程
        34.killasgroup=false ;默认为false,向进程组发送kill信号,包括子进程

        35.[include] ;包含其它配置文件
        36.files = relative/directory/*.ini ;可以指定一个或多个配置文件
8.5.4.启动:supervisord -c /etc/supervisord.conf
8.5.5.开机启动
    a)新建“supervisord.service”文件
    b)内容如下
        [Unit]
        Description=Supervisor daemon
        [Service]
        Type=forking
        ExecStart=/usr/bin/supervisord -c /etc/supervisor/supervisord.conf
        ExecStop=/usr/bin/supervisorctl shutdown
        ExecReload=/usr/bin/supervisorctl reload
        KillMode=process
        Restart=on-failure
        RestartSec=42s
        [Install]
        WantedBy=multi-user.target
    c)将文件拷贝至:“/usr/lib/systemd/system/supervisord.service”
    d)执行命令:systemctl enable supervisord
    e)执行命令:systemctl is-enabled supervisord #来验证是否为开机启动
8.5.6.supervisorctl
    a)supervisorctl status:查看所有进程的状态
    b)supervisorctl stop test:停止test
    c)supervisorctl start test:启动test
    d)supervisorctl restart test: 重启test
    e)supervisorctl update:配置文件修改后可以使用该命令加载新的配置
    f)supervisorctl reload: 重新启动配置中的所有程序
    g)把test换成all 可以管理配置中的所有进程
    h)直接输入:supervisorctl 进入supervisorctl 的shell交互界面,上述命令不带supervisorctl可直接使用
8.5.7.实践
    问题1:command中指定的进程已经起来,但supervisor还不断重启
    办法1:supervisor不能监控后台进程,command 不能为后台运行命令
    问题2:启动了多个supervisord服务,导致无法正常关闭服务
    办法2:使用 ps -ef | grep test 查看所有启动过的supervisord服务,kill相关的进程
8.6.K8S+Docker:这种方式一般就规模化部署了,由于涉及到的知识面较广,此处不啰嗦了。
小结:针对应用程序的部署有多种方式和选择,如果仅仅想把ASP.NET升级到ASP.NET CORE,不改变部署方式(一般部署在IIS上),那就容易很多了。

9.扩展信息
9.1.性能测试
9.1.1.说明1:下属测试结果来自https://blog.csdn.net/weixin_29495899/article/details/116874607
9.1.2.说明2:主要测试的ASP.NET CORE版本是5.0,测试时间:2021年
9.1.3.测试结果:
    a)Windows + Kestrel (18808)
    b)Linux + Kestrel (10667)
    c)Windows + IIS In Process (10089)
    d)Linux + Nginx (3509)
    e)Linux + Caddy (3485)
    f)Windows + IIS Out of Process (2820)
9.1.4.结果说明:上述测试仅测试了一个输出字符串,并不能代表 ASP.NET Core 5.0 及各服务器性能表现的全部,在实际项目中,影响性能的因素非常多。

10.总结

在Asp.net Core的编程模式中一切皆依赖注入。Asp.net Core相比较Asp.net开放更多知识,致使一众.Neter或想学.Net/C#的童鞋一时无法转变思维,开放更多知识对程序员来说是一件好事,我们不能习惯于做代码搬运工,应该向国外程序员一样学习更底层知识和技术。很多人都感叹.NET变化太快学不过来,干脆转Java等其他语言,其实我并不这么认为,Java那边变化就不快吗?变化速度之快完全不亚于.NET,如果因为变化快而转换编程语言,那么就应该认真思考自己是否适合计算机编程这份工作了,说句玩笑话,如果说真正变化快的,那还要说web前端技术的变化速度O(∩_∩)O,只要抓住不变的任其变化好了。Asp.Net这种比较彻底的转换升级,我认为是一件好事,不用背负沉重的历史包袱,轻装上阵反而更甚一筹,当然,如果是从以前的框架升级过来,感觉就更加爽了,因为在实践和追求技术过程中期望的解决方案,终于出现了。我相信Asp.Net到达Core时代,很长一段时间都不会再有大的改变,因此是时候进入Core时代了。


11.附录
https://www.cnblogs.com/artech/tag/ASP.NET%20Core/
https://www.cnblogs.com/ants/p/5732337.html
https://blog.csdn.net/zou79189747/article/details/80403016