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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章