.NET Generic Host 是 .NET Core 發展出來的基礎建設,可以和其他類型的 .NET 應用程式搭配使用例如背景服務的主控台應用程式,Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder 靜態方法,它來自於 Microsoft.Extensions.Hosting.dll,主要用來提供應用程式一個標準的啟動,包含注入、紀錄、組態,不同的應用程式框架 (HostService) 有不同的預設啟動設定,.NET Generic Host 讓我們的應用程式的生命週期的控制,啟動到結束的撰寫方式統一了。
開發環境
- Rider 2021.3.4
- Windows 10
- .Net Fx 4.8 via 新版專案範本 .NET Project SDKs
- Microsoft.Extensions.Hosting 5.0.0
Microsoft.Extensions.Hosting 適用於 .NET Fx 4.6.1 以上
Install-Package Microsoft.Extensions.Hosting -Version 5.0.0
實例化 IHostBuilder
Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder 靜態方法提供了應用程式預設的啟動設定,只要呼叫就能完成設定,主要的設定如下:
官方文件的重點整理再搭配原始碼,應該會更清楚的知道 Host.CreateDefaultBuilder 內部幫我們做了哪些事。
Host.CreateDefaultBuilder
- 實例化 HostBuilder
- 指定主機要使用的內容根目錄,設定 GetCurrentDirectory()
- 載入主機組態 (Host configuration):
- 環境變數前綴為 DOTNET_
若使用 ConfigureWebHostDefaults,ASPNETCORE_ENVIRONMENT 的值會覆寫 DOTNET_ENVIRONMENT - 命令列組態
- 環境變數前綴為 DOTNET_
- 載入應用程式組態 (ASP.NET Core/Console App):
- appsettings.js。
- appsettings.{Environment}.json
- 應用程式在 環境中執行時的 Development 祕密管理員。
- 環境變數。
- 命令列引數。
- 新增下列記錄提供者:
- 主控台
- 偵錯
- EventSource
- EventLog (僅當在 Windows 上執行時)
- 當環境為時,可啟用範圍驗證和相依性 驗證 Development 。
IHostBuilder 擴充方法
以下這些擴充方法用來設定應用程式其他的設定,可多次呼叫,結果會累加(後面蓋掉前面)。
- ConfigureServices:將物件(服務) 加入 DI Container
- ConfigureLogging:加入 Log Provider
- ConfigureAppConfiguration:設定其餘的組態
- ConfigureWebHostDefaults:設定 Web 應用程式的預設值
更多的擴充方法請「參考」
實例化 IHostedService
IHostedService 是用來實現來完成應用程序的"工作",當 Host 啟動後,也就是呼叫 IHostedService.StartAsync,這時它會執行 DI Container 裡已註冊的所有 BackgroundService、IHostedService 的物件和 BackgroundService.ExecuteAsync 方法
BackgroundService 實作 IHostedService,BackgroundService 擁有更多控制服務的能力,比如長時間的背景執行作業調用 BackgroundService.ExecuteAsync
預設自動注入以下:
- IHostLifetime:控制主機啟動及停止的時機
- IHostEnvironment:主機執行的屬性
- IHostApplicationLifetime:控制應用程式啟動和關閉事件處理程序方法
LabHostedService 實作 IHostedService 並開啟 ILogger、 IHostApplicationLifetime、IHostLifetime、IHostEnvironment 注入點,範例如下:
public class LabHostedService : IHostedService
{
private readonly ILogger _logger;
public LabHostedService(ILogger<LabHostedService> logger,
IHostApplicationLifetime appLifetime,
IHostLifetime hostLifetime,
IHostEnvironment hostEnvironment)
{
this._logger = logger;
appLifetime.ApplicationStarted.Register(this.OnStarted);
appLifetime.ApplicationStopping.Register(this.OnStopping);
appLifetime.ApplicationStopped.Register(this.OnStopped);
this._logger.LogInformation($"主機環境:" +
$"ApplicationName = {hostEnvironment.ApplicationName}\r\n" +
$"EnvironmentName = {hostEnvironment.EnvironmentName}\r\n" +
$"RootPath = {hostEnvironment.ContentRootPath}\r\n" +
$"Root File Provider = {hostEnvironment.ContentRootFileProvider}\r\n");
}
public Task StartAsync(CancellationToken cancellationToken)
{
this._logger.LogInformation("1. 調用 Host.StartAsync ");
return Task.CompletedTask;
}
private void OnStarted()
{
this._logger.LogInformation("2. 調用 OnStarted");
}
private void OnStopping()
{
this._logger.LogInformation("3. 調用 OnStopping");
}
public Task StopAsync(CancellationToken cancellationToken)
{
this._logger.LogInformation("4. 調用 Host.StopAsync");
return Task.CompletedTask;
}
private void OnStopped()
{
this._logger.LogInformation("5. 調用 OnStopped");
}
}
LabBackgroundService 實作 BackgroundService,多了一個 ExecuteAsync 方法
public class LabBackgroundService : BackgroundService
{
private readonly ILogger<LabBackgroundService> _logger;
public LabBackgroundService(ILogger<LabBackgroundService> logger,
IHostApplicationLifetime appLifetime,
IHostLifetime hostLifetime,
IHostEnvironment hostEnvironment)
{
this._logger = logger;
appLifetime.ApplicationStarted.Register(this.OnStarted);
appLifetime.ApplicationStopping.Register(this.OnStopping);
appLifetime.ApplicationStopped.Register(this.OnStopped);
this._logger.LogInformation($"主機環境:" +
$"ApplicationName = {hostEnvironment.ApplicationName}\r\n" +
$"EnvironmentName = {hostEnvironment.EnvironmentName}\r\n" +
$"RootPath = {hostEnvironment.ContentRootPath}\r\n" +
$"Root File Provider = {hostEnvironment.ContentRootFileProvider}\r\n");
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
this._logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
await Task.Delay(1000, stoppingToken);
}
}
public Task StartAsync(CancellationToken cancellationToken)
{
this._logger.LogInformation("1. 調用 Host.StartAsync ");
return Task.CompletedTask;
}
private void OnStarted()
{
this._logger.LogInformation("2. 調用 OnStarted");
}
private void OnStopping()
{
this._logger.LogInformation("3. 調用 OnStopping");
}
public Task StopAsync(CancellationToken cancellationToken)
{
this._logger.LogInformation("4. 調用 Host.StopAsync");
return Task.CompletedTask;
}
private void OnStopped()
{
this._logger.LogInformation("5. 調用 OnStopped");
}
}
在 Program.cs 調用以下
- Host.CreateDefaultBuilder(args):實例化 HostBuilder
- services.AddHostedService:注入 HostService
internal class Program
{
private static Task Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
var task = host.RunAsync();
Console.WriteLine($"{nameof(LabHostedService)} 應用程式已啟動");
return task;
}
private static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.ConfigureServices((hostBuilder, services) =>
{
services.AddHostedService<LabHostedService>();
services.AddHostedService<LabBackgroundService>();
Console.WriteLine("注入HostService");
});
}
}
IHostLifetime
IHostLifetime 實現來管理應用程序的生命週期,目前都是由微軟官方實作
runtime/IHostLifetime.cs at main · dotnet/runtime (github.com)
IHostLifetime 有以下兩種方法
public interface IHostLifetime
{
Task WaitForStartAsync(CancellationToken cancellationToken);
Task StopAsync(CancellationToken cancellationToken);
}
- WaitForStartAsync 在 Generic Host 啟動時被調用,可用於啟動偵聽關閉事件或延遲應用程序的啟動,直到發生某些事件為止。
- StopAsync Generic Host 停止時調用。
IHostLifetime 目前存在三種不同的實現:
- ConsoleLifetime:監聽 Ctrl + C、 SIGINT 或 SIGTERM 停止主機應用程序,預設 .NET Generic Host 會啟動它
- SystemdLifetime:監聽 SIGTERM 並停止主機應用程序,並通知 systemd 狀態更改(Ready和 Stopping)。
- WindowsServiceLifetime:掛鉤 Windows Service 事件以進行生命週期管理
以 Windows Service 來講就是按下服務的啟動 / 停止
ConsoleLifetime
.NET Generic Host 啟動時,預設啟動 ConsoleLifetime 來處理 Host 的生命週期。在ConsoleLifetime 內主要監聽四個事件:
- ApplicationLifetime.ApplicationStarted
- ApplicationLifetime.ApplicationStopping
- AppDomain.CurrentDomain.ProcessExit
- Console.CancelKeyPress
原始碼:runtime/ConsoleLifetime.cs at release/5.0 · dotnet/runtime (github.com)
public Task WaitForStartAsync(CancellationToken cancellationToken)
{
...
// Attach event handlers for SIGTERM and Ctrl+C
AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
Console.CancelKeyPress += OnCancelKeyPress;
// Console applications start immediately.
return Task.CompletedTask;
}
GenericWebHostService
ASP.NET Core 3.1 之後則是使用 GenericWebHostService,預設使用 IHostBuilder.ConfigureWebHostDefaults 擴充方法建立 Host
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>(); });
}
.NET Generic Host 啟動後,GenericWebHostService.StartAsync 方法會啟動 IServer. StartAsync() 聆聽Http封包,並通過 HostingApplication 管道處理接著往下處理
IHostBuilder.ConfigureWebHostDefaults 擴充方法裡面再去幫我們註冊 GenericWebHostService 到 DI Container
public static IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action<IWebHostBuilder> configure, Action<WebHostBuilderOptions> configureWebHostBuilder)
{
if (configure is null)
{
throw new ArgumentNullException(nameof(configure));
}
if (configureWebHostBuilder is null)
{
throw new ArgumentNullException(nameof(configureWebHostBuilder));
}
var webHostBuilderOptions = new WebHostBuilderOptions();
configureWebHostBuilder(webHostBuilderOptions);
var webhostBuilder = new GenericWebHostBuilder(builder, webHostBuilderOptions);
configure(webhostBuilder);
builder.ConfigureServices((context, services) => services.AddHostedService<GenericWebHostService>());
return builder;
}
啟動循序圖
關閉循序圖
IHostApplicationLifetime
當主機啟動後,由開發人員撰寫的控制點,有以下控制點
- IHostApplicationLifetime.ApplicationStarted 屬性:當應用程式主機完全啟動時觸發。
- IHostApplicationLifetime.ApplicationStopping 屬性 :當應用程式主機執行順利關機時觸發此事件,堵塞關閉事件,直到事件完成為止。
- IHostApplicationLifetime.ApplicationStopped 屬性:當應用程式主機執行順利關機時觸發此事件,堵塞關閉事件,直到事件完成為止。
- IHostApplicationLifetime.StopApplication 方法 :關閉主機。
註冊寫法如下:
public LabHostedService(ILogger<LabHostedService> logger,
IHostApplicationLifetime appLifetime,
IHostLifetime hostLifetime,
IHostEnvironment hostEnvironment)
{
this._logger = logger;
appLifetime.ApplicationStarted.Register(this.OnStarted);
}
private void OnStarted()
{
this._logger.LogInformation("2. 調用 OnStarted");
}
IHostEnvironment
主機環境資訊
- IHostEnvironment.ApplicationName
- IHostEnvironment.ContentRootFileProvider
- IHostEnvironment.ContentRootPath
- IHostEnvironment.EnvironmentName
Host Configuration
用來設定 IHostEnvironment 物件的屬性,透過 ConfigureAppConfiguration 設定
當呼叫 ConfigureAppConfiguration 方法時,configureDelegate 參數(定義為 Action<HostBuilderContext, IConfigurationBuilder>)
- HostBuilderContext.Configuration:代表主機的組態實例。
- IConfigurationBuilder:用來設定應用程式組態的 Builder。
呼叫 ConfigureAppConfiguration 之後,將覆蓋原本的 HostBuilderContext.Configuration。
以 IHostEnvironment.EnvironmentName 為例,預設是使用 DOTNET_ 開頭的環境變數 DOTNET_ENVIRONMENT,取 DOTNET_ 前綴詞得到 ENVIRONMENT,ENVIRONMENT 則是 Configuration Key,它代表著 IHostEnvironment.EnvironmentName 的結果。
在 ASP.NET Core 會被改成以 ASPDOTNET_ 開頭的環境變數,將會覆蓋 DOTNET_ 開頭的環境變數
在 appsettings.json增加 Environment 節點
{
"ConnectionStrings": {
"DefaultConnectionString": "Server=(localdb)\\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True;"
},
"Player": {
"AppId": "player1",
"Key": "1234567890"
},
"Environment": "Development"
}
替換主機組態,範例如下:
[TestMethod]
public void 設定主機組態()
{
var builder = Host.CreateDefaultBuilder()
.ConfigureHostConfiguration(config=>
{
config.AddJsonFile("appsettings.json", false, true);
})
;
var host = builder.Build();
var environment = host.Services.GetRequiredService<IHostEnvironment>();
Console.WriteLine($"EnvironmentName={environment.EnvironmentName}");
}
執行結果如下:
EnvironmentName = Development
範例位置
sample.dotblog/Host/Lab.MsHost/ConsoleAppNetFx48 at master · yaochangyu/sample.dotblog (github.com)
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET