ASP.NET Core 6框架揭秘實例演示[22]:如何承載你的後台服務[補充]

藉助 .NET提供的服務承載(Hosting)系統,我們可以將一個或者多個長時間運行的後台服務寄宿或者承載我們創建的應用中。任何需要在後台長時間運行的操作都可以定義成標準化的服務並利用該系統來承載,ASP.NET Core應用最終也體現為這樣一個承載服務。(本篇提供的實例已經匯總到《ASP.NET Core 6框架揭秘-實例演示版》)

[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方法關閉的。

image
圖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服務將自動被創建並啟動(S1408)。

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所示的輸出結果表明,應用程式當前的承載環境與基於宿主的配置是一致的。

image
圖2 利用配置來初始化承載環境