上一篇使用 Topshelf 處理了啟動 Windows Service 時要環境參數的問題,為了讓之後用 Topshelf 寫 Windows Service 時,有更一致的寫法,這篇將使用使用 Topshelf 與 .NET 泛型主機架構,來建立專案架構。
之所以想搭配 .NET 泛型主機的架構來建立專案,除了這是個規劃優良的 Builder Pattern 建造者模式,這個架構支援相依性注入、組態設定以及紀錄器等方便後續開發的核心功能,而且 ASP .NET Core 的架構也是使用這樣的架構,因此可以用同樣的思維來開發專案,這個專案架構規劃好之後用起來一定會很順手。
起手式 Console 專案
用 Topshelf 開發的 Windows Service 本質是個 Console 應用程式,當然就直接建立一個 Console 專案,這邊主要處理兩件事:
第一件事就是安裝 Microsoft.Extensions.Hosting 和 Topshelf 套件,前者用來打造泛型主機架構,後者就是開發 Windows Service 囉。
第二件事則是建立設定檔,通常我們會根據環境來套用不同的設定檔,因此這裡我們建立兩個設定檔 appsettings.json 和 appsettings.Development.json,前者是通用的設定檔,我通常把它當成範本,而後者是當 DOTNET_ENVIRONMENT 環境變數是 Development 時,會套用此設定檔。
為什麼是看
DOTNET_ENVIRONMENT這個環境變數,主要是因為等一下用Microsoft.Extensions.Hosting所提供的預設泛型主機架構時,他預設就會抓DOTNET_開頭的環境變數,因此會將使用這個環境變數做為來取用不同”環境”(Environment)的設定檔。
1 | <Project Sdk="Microsoft.NET.Sdk"> |
建立 .NET 泛型主機架構
首先我們先建立 CreateHostBuilder() 方法,方便之後使用,在這個方法中使用 Microsoft.Extensions.Hosting 命名空間中所提供的 Host 泛型主機類別,這個類別預先提供了 CreateDefaultBuilder() 建立預設設定好哦的泛型主機架構,預先作了以下這些事:
- 設定泛型主機的預設跟目錄為
Directory.GetCurrentDirectory() - 泛型主機載入執行環境中
DOTNET_開頭的環境變數 - 泛型主機載入執行時傳入的啟動參數,也就是
Main()的args - 應用程式載入
appsettings.json和appsettings.Environment.json設定檔,後者的Environment會去抓DOTNET_ENVIRONMENT這個環境變數設定值 - 應用程式載入
secrets.json使用者機密設定,詳請參考如何使用 Secret Manager 保護 .NET Core 專案的機密設定 - 應用程式載入環境變數
- 應用程式載入啟動參數
- 設定
ILoggerFactory紀錄器
有了預設的設定,我們還是要稍微修改一些東西:
- 將啟動參數
env當作DOTNET_ENVIRONMENT環境變數 - 設定根目錄
第一點是因為我希望啟動 Windows Service 時,能使用參數來設定環境變數,因為我這邊有些環境不能調整伺服器上的環境變數,程式碼如下(GetArgumentValue() 是我另外寫的解析方法,完整內容請參考最下面的範例專案連結):
1 | Environment.SetEnvironmentVariable("DOTNET_ENVIRONMENT", GetArgumentValue(args, "env")); |
第二點是避免遠端啟動服務時,應用程式抓錯根目錄,造成設定檔載入失敗的問題,因此再設定一次跟目錄位置,程式碼如下:
1 | config.SetBasePath(AppContext.BaseDirectory); |
設定完之後,就可以在 ConfigureServices() 裡面設定依賴注入了,基本上我通常至少會注入兩個東西,AppSettings 設定檔實體和 App 主要邏輯程式。
而 App 類別會做為下一階段 Topshelf 所使用的主要邏輯程式,所以這類別會提供 Start() 和 Stop() 做為 Topshelf 執行或停止主要邏輯程式的動作。
最後一步就是在 Main() 中呼叫泛型主機建造者的 Build() 方法,將剛剛設定的東西實體化,這階段完整的程式碼如下:
1 | public static void Main(string[] args) |
建立 Windows Service
這篇的另一個重點,使用 Topshelf 建立 Windows Service,這裡我建立了 Startup 類別做為 Windows Service 程式架構的核心,ActivateTopshelf() 是 Topshelf 架構 Windows Service 的重點部分,這邊你可以根據你 Windows Service 的需要做調整,下面的程式碼已經根據傳遞參數來啟動 Windows Service (使用 Topshelf 實作)這篇文章,做了使用啟動參數做為環境變數設定的調整。
這樣主程式呼叫 RunWindowsServiceWithHost() 靜態方法的時候,就會透過 Topshelf 來執行安裝或是啟動 Windows Service,另外 Topshelf 在執行時,會返回狀態碼,這部分也做了相對應的判斷,讓之後的判斷能方便些。
在 Topshelf 的 HostFactory.Run() 裡面,我們會使用前面泛型主機所註冊的 App 做為主要邏輯的進入點,使用 Host.Services.GetRequiredService<App>() 來調用。
GetRequiredService<T>()是 .NET Core 內建 DI 容器的方法,這方法確保了該類別已經完成實體化,並且是可以調用的狀態。
1 | public class Startup |
完整的專案架構
最後在回到 Main() 中,將 Topshelf 的使用加上去,這樣就完成整個專案架構了,而如果執行時有指定環境變數,執行時可以傳入 -env:Debug 像這樣的參數來設定執行環境。
1 | public static void Main(string[] args) |
最後回顧一下整個專案架構,可分成 3 部分:
CreateHostBuilder()這是 .NET 泛型主機的主要部分,會在這裡設定相依性注入,所依賴的服務也會在這邊註冊Startup類別是 Topshelf 安裝和啟動 Windows Service 的主要部分,這裡設定完後不太會更動,因為主要執行的邏輯已經抽到App這個類別裡面App這裡就是我們設計、開發主要商業邏輯的地方
本篇完整範例程式碼請參考 poychang/Demo-Topshelf-With-Generic-Host。
參考資料: