借助 .NET提供的服務承載(Hosting)系統,我們可以將一個或者多個長時間運行的后臺服務寄宿或者承載我們創建的應用中。任何需要在后臺長時間運行的操作都可以定義成標準化的服務并利用該系統來承載,ASP.NET Core應用最終也體現為這樣一個承載服務。[本文節選《ASP.NET Core 6框架揭秘》第14章]
[S1407]利用IHostApplicationLifetime對象關閉應用(源代碼)
[S1408]與第三方依賴注入框架的整合(源代碼)
[S1409]利用配置初始化承載環境(源代碼)
[S1407]利用IHostApplicationLifetime對象關閉應用
我們接下來通過一個簡單的實例演示如何利用IHostApplicationLifetime服務來關閉整個承載應用。我們在一個控制臺應用程序中定義了如下這個承載服務類型FakeHostedService,并在其構造函數中注入了IHostApplicationLifetime服務。在得到其三個屬性返回的CancellationToken對象之后,我們在它們上面分別注冊了一個回調在控制臺輸出相應的文字。
public?sealed?class?FakeHostedService?:?IHostedService
{private?readonly?IHostApplicationLifetime?_lifetime;private?IDisposable??_tokenSource;public?FakeHostedService(IHostApplicationLifetime?lifetime){_lifetime?=?lifetime;_lifetime.ApplicationStarted.Register(()?=>?Console.WriteLine("[{0}]Application?started",?DateTimeOffset.Now));_lifetime.ApplicationStopping.Register(()?=>?Console.WriteLine("[{0}]Application?is?stopping.",?DateTimeOffset.Now));_lifetime.ApplicationStopped.Register(()?=>?Console.WriteLine("[{0}]Application?stopped.",?DateTimeOffset.Now));}public?Task?StartAsync(CancellationToken?cancellationToken){_tokenSource?=?new?CancellationTokenSource(TimeSpan.FromSeconds(5)).Token.Register(_lifetime.StopApplication);return?Task.CompletedTask;}public?Task?StopAsync(CancellationToken?cancellationToken){_tokenSource?.Dispose();return?Task.CompletedTask;}
}
在實現的StartAsync方法中,我們采用如上的方式在等待5秒之后調用IHostApplicationLifetime對象的StopApplication方法關閉應用程序。FakeHostedService服務最后采用如下所示的方式承載于當前應用程序中。
using?App;
Host.CreateDefaultBuilder(args).ConfigureServices(svcs?=>?svcs.AddHostedService<FakeHostedService>()).Build().Run();
該程序運行之后在控制臺上輸出的結果如圖1所示,從三條消息輸出的時間間隔可以確定當前應用程序正是承載FakeHostedService通過調用IHostApplicationLifetime服務的StopApplication方法關閉的。
圖1 調用IHostApplicationLifetime服務關閉應用程序
[S1408]與第三方依賴注入框架的整合
《一個Mini版的依賴注入框架》中創建了一個名為Cat的簡易版依賴注入框架,并在《與第三方依賴注入框架Cat的整合》中為其創建了一個IServiceProviderFactory<TContainerBuilder>實現類型,具體類型為CatServiceProvider,我們接下來演示一下如何通過注冊CatServiceProvider實現與Cat這個第三方依賴注入框架的整合。在創建的演示程序中,我們采用這樣的方式定義了三個服務(Foo、Bar和Baz)和對應的接口(IFoo、IBar和IBaz),并在服務類型上標注MapToAttribute特性來定義服務注冊信息。
public?interface?IFoo?{?}
public?interface?IBar?{?}
public?interface?IBaz?{?}[MapTo(typeof(IFoo),?Lifetime.Root)]
public?class?Foo?:??IFoo?{?}[MapTo(typeof(IBar),?Lifetime.Root)]
public?class?Bar?:??IBar?{?}[MapTo(typeof(IBaz),?Lifetime.Root)]
public?class?Baz?:??IBaz?{?}
如下所示的FakeHostedService類型表示承載的服務。我們在構造函數中注入了IFoo、IBar和IBaz對象,構造函數提供的調試斷言用于驗證上述三個服務被成功注入。
public?sealed?class?FakeHostedService:?IHostedService
{public?FakeHostedService(IFoo?foo,?IBar?bar,?IBaz?baz){Debug.Assert(foo?!=?null);Debug.Assert(bar?!=?null);Debug.Assert(baz?!=?null);}public?Task?StartAsync(CancellationToken?cancellationToken)?=>?Task.CompletedTask;public?Task?StopAsync(CancellationToken?cancellationToken)?=>?Task.CompletedTask;
}
我們在如下的演示程序中創建了一個IHostBuilder對象,通過調用其ConfigureServices方法注冊了需要承載的FakeHostedService服務后,我們調用它的UseServiceProviderFactory方法完成了對CatServiceProvider的注冊。我們隨后調用了CatBuilder的Register方法完成了針對入口程序集的批量服務注冊。調用IHostBuilder的Build方法構建出作為宿主的IHost對象并啟動它之后,承載的FakeHostedService服務將自動被創建并啟動。
using?App;
using?System.Reflection;Host.CreateDefaultBuilder().ConfigureServices(svcs?=>?svcs.AddHostedService<FakeHostedService>()).UseServiceProviderFactory(new?CatServiceProviderFactory()).ConfigureContainer<CatBuilder>(builder?=>?builder.Register(Assembly.GetEntryAssembly()!)).Build().Run();
[S1409]利用配置初始化承載環境
一個HostBuilderContext上下文由承載針對宿主配置的IConfiguration對象和描述當前承載環境的IHostEnvironment對象組成,后者提供的環境名稱、應用名稱和內容文件根目錄路徑可以通過前者來指定,具體的配置項名稱定義在如下這個靜態類型HostDefaults中。
public?static?class?HostDefaults
{public?static?readonly?string?EnvironmentKey?=?"environment";public?static?readonly?string?ContentRootKey?=?"contentRoot";public?static?readonly?string?ApplicationKey?=?"applicationName";
}
下面我們通過一個簡單的實例演示如何利用配置的方式來指定上述三個與承載環境相關的屬性。我們定義了如下一個名為FakeHostedService的承載服務,并在構造函數中注入IHostEnvironment對象。FakeHostedService派生于抽象類BackgroundService,我們在在ExecuteAsync方法中將與承載環境相關的環境名稱、應用名稱和內容文件根目錄路徑輸出到控制臺上。
public?class?FakeHostedService?:?BackgroundService
{private?readonly?IHostEnvironment?_environment;public?FakeHostedService(IHostEnvironment?environment)?=>?_environment?=?environment;protected?override?Task?ExecuteAsync(CancellationToken?stoppingToken){Console.WriteLine("{0,-15}:{1}",?nameof(_environment.EnvironmentName),?_environment.EnvironmentName);Console.WriteLine("{0,-15}:{1}",?nameof(_environment.ApplicationName),_environment.ApplicationName);Console.WriteLine("{0,-15}:{1}",?nameof(_environment.ContentRootPath),_environment.ContentRootPath);return?Task.CompletedTask;}
}
FakeHostedService采用如下形式進行承載。如代碼片段所示,為了避免輸出日志的“干擾”,我們調用IHostBuilder接口的ConfigureLogging擴展方法將注冊的ILoggerProvider對象全部清除。如果調用Host靜態類型的CreateDefaultBuilder方法時傳入當前的命令行參數,創建的IHostBuilder對象會將其作為配置源,所以我們就能以命令行參數的形式來指定承載上下文的三個屬性。
using?App;
Host.CreateDefaultBuilder(args).ConfigureLogging(logging?=>logging.ClearProviders()).ConfigureServices(svcs?=>?svcs.AddHostedService<FakeHostedService>()).Build().Run();
我們采用命令行的方式啟動這個演示程序,并利用傳入的命令行參數指定環境名稱、應用名稱和內容文件根目錄路徑(確保路徑確實存在)。圖2所示的輸出結果表明,應用程序當前的承載環境與基于宿主的配置是一致的。
圖2 利用配置來初始化承載環境