本篇已收錄至 asp.net core 隨筆系列
通過閱讀本文, 希望能夠對以下問題有一些思路:
- ASP.Net Core web 應用程序的啟動方式?
- 程序如果是通過命令行啟動的, 可以添加 args 參數, 這些參數是如何傳遞到底層的?
- Host 是做什么用的?
- 在自動生成的 code 中可以看到 appSettings.json 以及 appSettings.Development.Json, 我們知道是對不同的模式的 settings. 那么底層是怎么實現讀取的?
- 相應的, 不同的環境mode, 的log的配置也是不一樣的. 底層是如何做到區分的? 默認 asp.net core 3.0 有哪些提供的 logger?
- asp.net core web server 使用的是什么? 什么時候配置的?
- 有哪些中間件是在 web Host 構建時就配置了?
- Startup.cs 文件的使用原理?
- 底層的IOC的容器是如何初始化的
程序啟動的入口
使用vs創建 asp.net core(以下簡稱anc)的web項目里面有個 program.cs 文件. 文件內容很簡單:
anc web app 項目實際是一個 console app, 所以有 main 函數, 程序的入口就是 main 函數
main 函數就做了一些"簡單微小"的工作:
- 創建HostBuilder
- 'HostBuilder'在真正'Build'出Host之前還做了什么?
- HostBuilder執行Build()將Host構建出來
- IHostBuilder.Build()
- Host.Run()
構建 HostBuilder
這個小節主要是圍繞這段代碼展開討論:
Host.CreateDefaultBuilder(args)
Host 是一個靜態類, 用其 CreateDefaultBuilder
方法可以創建一個 HostBuilder 實例 (IHostBuilder).
3.0 以前是 WebHostBuilder, 3.0 以后給原來的 WebHostBuilder 外面包裹了一層就叫 HostBuilder. 可能微軟希望將 Host 這個概念抽象出來, 以后可能不止是 web host, 還有 XX host.
CreateDefaultBuilder(), 微軟給提供的說明中寫道: Initializes a new instance of the Microsoft.Extensions.Hosting.HostBuilder class with pre-configured defaults.
, 這里需要了解一下配置的方式, 因為在 Server 真正啟動之前的所有的操作其實都是在各種配置與嵌套配置當中徘徊. 下圖是方法的底層源碼大致的內容:
方法的返回值是 HostBuilder, 在返回之前進行了一系列的配置, 咱們單獨拿出一個舉例, 比如 ConfigureHostConfiguration, 看名字可以知道是配置Host的一些Configuration的:
參數是委托類型的實例, 代碼內部將委托實例添加到一個泛型的類型也是委托類型的集合當中:
_configureHostConfigActions 這個字段是 HostBuilder 的實例的眾多字段的一個, 有朋友要問, 都放到集合里, 啥時候執行這個委托里面的代碼真正的進行配置呢? 這個問題先放在這, 先看一下除了這個集合, HostBuilder中還有什么集合:
這些集合都是通過 builder 調用不同的 Configure 方法, 或者其他的方法, 將想要配置的委托實例作為參數傳遞, 再添加到集合當中的. 然后我們回答上面提出的問題, 看看是什么時候將這些集合中的委托實例取出執行每個委托進來的代碼的:
集合的委托實例遍歷執行后, 得到的 configBuilder 再次 Build 后賦值給 _hostConfiguration. 同樣也是 HostBuilder 類中的一個字段, 類似的字段還有好幾個如圖:
我們分別介紹這幾個字段的初始化方式以及對應的作用.
-
_hostingEnvironment 與 _hostConfiguration 會被用於初始化 _hostBuilderContext:
-
_hostBuilderContext 的實例最后會被添加到 anc 內置的容器中, 開發者可以隨時獲取到這個實例, 進而獲取各項配置.
-
HostingEnvironment 也是同理, 最后會被添加到容器中.
-
_appServices 就是我們熟知的容器了. 創建容器也是 HostBuilder.Build() 方法中最后執行的一步:
這種先添加到緩存再進行配置的意義在於, 對於我們開發人員, 可以使用HostBuilder在真正調用Build()以前, 調用想用的任何配置對其進行配置, 即使配置重復了, 因為是向集合中添加委托實例, 最后執行代碼的時候, 相同的配置項, 后加入的也會覆蓋前面的.
總的說來 CreateDefaultBuilder() 創建 HostBuilder 做了這么幾件事兒:
- 設置 IHostEnvironment.ContentRootPath
- 從環境變量中獲取前綴為
DOTNET_
的配置項配置 IConfiguration - 從命令行參數 args 中獲取配置項配置 IConfiguration
- 從配置文件 'appsettings.json'以及'appsettings.[Microsoft.Extensions.Hosting.IHostEnvironment.EnvironmentName].json'中獲取配置項配置 IConfiguration
- 配置 log provider 默認為 console, debug,以及 event source output
- ... 省略我不會的
HostBuilder在真正Build出Host之前還做了什么?
沒錯, 在上一小節, 其實我們已經創建出 HostBuilder, 還擴展透露了一些 HostBuilder 調用 Build() 做的事情. 但是 anc 框架自動為我們生成的代碼中不是馬上就執行了Build方法, 而是繼續進行額外的配置, 這個配置又起到了什么作用呢? 不配置的話, anc的項目還能夠正確運行嗎? 帶着這個疑問, 繼續閱讀吧.
微軟對於這個方法的說明已經解釋了為什么一定要繼續配置一下這個WebHostBuilder, 因為在這里才是真正加載內置的Web server. 而且能夠跨平台也是因為有這個web server.
下面是這個小節的重點 !!! 接下來是詳細解析這張圖的環節
-
第一步, HostBuilder 調用 ConfigureWebHost.
-
第二步, ConfigureWebHost 方法內, 首先初始化一個 GenericWebHostBuilder (IWebHostBuilder), 初始化這個 web host builder 很重要. 因為webhost是真正擁有startupLoader的類.
-
第三步, 將第二步初始化出的 webHostBuilder 傳入委托方法中執行委托代碼.
-
第四步, WebHost.ConfigureWebDefaults(webHostBuilder), 其實就是配置 Kestrel 作為 web server等等
-
第五步, 繼續將 webHostBuilder 傳入委托方法中執行委托代碼, 此時的委托代碼為 Program.cs 文件中傳進來的委托代碼.
-
最后是將 GenericWebHostService 的實例添加到容器中. 然后返回經過配置了WebHostBuilder 的 HostBuilder
builder.ConfigureServices((context, services) => services.AddHostedService<GenericWebHostService>());
IHostBuilder.Build();
這部分沒什么好說的, 就是依次執行之前配置了那么多的委托代碼, 上文中我們已經解析過. 最后Build完返回的是
return _appServices.GetRequiredService<IHost>();
從 service collection 的容器內獲取 IHost 對應的實例返回.
這個實例是在CreateServiceProvider時添加進去的:
services.AddSingleton<IHost, Internal.Host>();
Host.Run()
值得注意的是這里有段代碼是將 webHost 啟動:
_hostedServices = Services.GetService<IEnumerable<IHostedService>>();
foreach (var hostedService in _hostedServices)
{
// Fire IHostedService.Start
await hostedService.StartAsync(combinedCancellationToken).ConfigureAwait(false);
}
這里的最終目的是將 web server 啟動. 最終能夠接收 http request 和 返回 http response.
總結
asp.net core web 啟動過程涉及到的底層部分主要分為幾個步驟:
創建 HostBuilder > 配置 HostBuilder > 配置 webHostBuilder > 創建 webHost > 開始 build host > host 啟動. 我畫了一張流程圖, 方便記憶: