最近遇到需求需要把控制台程式做成 windows服務 的形式運行。改成 windows服務 後,程式無法訪問共用網路文件,訪問提示“Access to the path '網路路徑' is denied.“,拒絕訪問,原因是當前服務的用戶憑證無許可權訪問,於是需要修改當前服務的用戶憑證,改為可訪問共用 ...
1. 入口文件
一個應用程式總有一個入口文件,是應用啟動代碼開始執行的地方,這裡往往也會涉及到應用的各種配置。當我們接觸到一個新框架的時候,可以從入口文件入手,瞭解入口文件,能夠幫助我們更好地理解應用的相關配置以及應用的工作方式。
.Net Core 應用的入口文件是 Program.cs,這裡是應用啟動的地方。在 .Net 6 之前的版本,Program.cs 文件是下麵這樣的,這是創建一個 Web 項目時的預設代碼。
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
其中 Main 方法就是應用啟動的入口。可以看到在應用啟動的時候,通過 建造者模式 創建了一個主機,併進行了相關的配置,最後將其運行起來。
從代碼中可以看到,在對主機進行配置的時候,使用到了 Startup 類,在 .Net 6 之前的版本,Startup 類承擔應用的啟動任務,是應用配置的主要地方。
2. Startup 類
2.1 Startup類結構
Startup 類支持兩種定義方式,一種是實現 IStartup 介面,一種是基於約定的。無論哪一種,Startup 類的基本結構都包含以下兩個個關鍵函數。整體來說,基於約定的 Startup 類更加靈活。
- ConfigureServices方法
- Configure方法
2.1.2 ConfigureServices 方法
- 該方法是可選的
- 該方法用於添加服務到DI容器中
- 該方法在 Configure 方法之前被調用
- 基於約定的情況下,該方法要麼無參數,要麼只能有一個參數且類型必須為 IServiceCollection
- 該方法內的代碼大多是形如
Add{Service}
的擴展方法
2.1.3 Configure方法
- 該方法是必須的
- 該方法用於配置 HTTP 請求管道,通過向管道添加中間件,應用不同的響應方式。
- 該方法在 ConfigureServices 方法之後被調用
- 基於約定的情況下,該方法中的參數可以接受任何已註入到DI容器中的服務
- 該方法內的代碼大多是形如
Use{Middleware}
的擴展方法 - 該方法內中間件的註冊順序與代碼的書寫順序是一致的,先註冊的先執行,後註冊的後執行
另外還有構造函數,當使用通用主機時,Startup 構造函數支持註入以下三種服務類型,在 Startup 類中全局進行使用:
- IConfiguration
- IWebHostEnvironment
- IHostEnvironment
2.2 預設Startup
所謂預設Startup,就是應用啟動配置不用 Startup 類,直接在 ConfigureWebHostDefaults 中進行配置。
publicstaticIHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
// ConfigureServices 可以調用多次,最終會將結果聚合
webBuilder.ConfigureServices(services =>
{
})
// Configure 如果調用多次,則只有最後一次生效.
.Configure(app =>
{
// Configure調用之前,ConfigureServices已經調用,容器對象已經生成,所以這裡可以通過容器直接解析需要的對象
var env = app.ApplicationServices.GetRequiredService<IWebHostEnvironment>();
});
});
2.3 多環境配置
.NET Core 框架支持多環境開發,可以通過環境變數 ASPNETCORE_ENVIRONMENT
來設置應用當前的運行環境,以實現一套代碼在不同環境下運行,根據環境區分一定的行為,支持開發、測試、預發佈、生成環境下不同條件、不同配置的運行場景。
我們可以直接在機器的環境變數中進行設置,在項目的 Properties 文件夾裡面的“launchSettings.json”文件進行配置,該文件是用於配置VS中項目啟動的,在 profiles 節點中通過不同的 json 對象配置當前應用的啟動模式,而描述啟動模式的 json 對象支持的欄位中有一個 environmentVariables
節點,可以通過鍵值對方式配置環境變數。
這裡配置的環境變數只會在當前項目中起作用。若應用運行環境中從未對 ASPNETCORE_ENVIRONMENT 環境變數進行配置,則預設為 Production 。而我們其實可以將 ASPNETCORE_ENVIRONMENT 設置為任意值。
之後,這些環境變數會在主機初始化的時候作為主機配置被載入到應用中,這些會在後面的配置系統中詳細講到。而在代碼中,我們可以通過註入 IWebHostEnvironment 服務獲取到當前應用的運行環境。例如下麵在 StartUp 中通過判斷環境執行不同的應用初始化邏輯。
IWebHostEnvironment 服務中預設提供對 Development、Production、Staging 三種環境進行判斷的擴展方法,如果是其他自定的環境,如 Test,可以使用 IsEnviroment() 方法進行判斷。
通過 IWebHostEnvironment 判斷不同環境,從而在 StartUp 類中使用不同的初始化初始化邏輯,這種方式適合於不同環境下代碼差異較少的情況。除此之外還有兩種基於約定的方式,分別是 Startup 方法約定 和 StartUp 類名約定 。
StartUp 方法約定具體是指 StartUp 類中 ConfigureServices 和 Configure 方法還可以按照Configure{EnvironmentName}Services和Configure{EnvironmentName}Services 這樣的命名格式來寫,通過命名約定的 {EnvironmentName} 部分區分不同環境,裝載不同環境的代碼。
如果 StartUp 類中存在與當前環境名稱匹配的 Configure{EnvironmentName}Services和Configure{EnvironmentName}Services 方法的話,則應用啟動時會執行相應的方法中的邏輯,如果沒有則執行原始的 ConfigureServices 和 Configure 方法中的邏輯。
通過查看源碼,可以看到當我們明確配置一個 Startup 類作為應用啟動類的時候,會先判斷是否是實現了 IStartup 介面。
如果沒有的話,則通過 StartupLoader 判斷 Startup 類是否符合約定,最終構建出實現了 IStartup 介面的ConventionBasedStartup,並註入到容器中。這時候會結合環境變數,優先獲取帶有環境變數的方法,如果沒有則使用沒有帶環境變數的方法。
而 StartUp 類名約定和方法約定類似,程式啟動時,會優先尋找當前環境命名符合Startup{EnvironmentName}的 Startup 類,如果找不到,則使用名稱為Startup的類。類名約定的方式適用於多環境下,代碼差異較大的情況。
類名約定的方式下,在配置使用 UseStartUp 的時候需要一點小改動:
查看源碼,可以看到在我們調用上面的方法的時候,實際上並沒有做具體的 Startup 類的構建操作,只是寫入了兩個設置,其實就是寫入到了配置系統中,其中 WebHostDefaults.StartupAssemblyKey 是關鍵。
之後在我們應用啟動,調用Build方法時,在構建ASP.NET Core 基本服務的時候才會根據設置去構建啟動類。
在這裡通過 StartupLoader 結合環境名稱查找程式集中符合約定的 Startup 類。
參考文章:
ASP.NET Core 系列總結:
目錄:ASP.NET Core 系列總結
下一篇:ASP.NET Core - IStartupFilter與IHostingStartup