概要
私の記事でも何度か触れていますが、GenericHost(WebHost)はとても便利な共通インフラなので、慣れると常に使っていきたくなります。しかし、さすがにコンソールアプリでは使えない・・・と思うかもしれませんが、使えます。コンソールアプリへのGenericHostの組み込み方を紹介します。
最初に結論まとめ
次のGitリポジトリのようにすることで組み込みができます。
説明
さっそく、組み込み方を説明していきます。
テンプレートから生成したコンソールアプリプロジェクトに、まずはGenericHostを追加します。NuGetで次のように追加です。
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.0" />
</ItemGroup>
最近のコンソールアプリのテンプレートならトップレベルのステートメントになっているので、Program.csの中身はHello Worldのコンソール出力だけだと思います。
そこを書き換えて、次のようにすることでホストを作成します。
_ = Host.CreateDefaultBuilder(args)
.RunConsoleAsync();
これだけだと何の処理も入れられないので、続けてメインのサービスとなるクラスを追加します。
サービスはIHostedServiceを継承すれば良いので、最小限だとこんな感じで、開始と停止のイベントハンドラだけになります。
public class ApplicationHostService : IHostedService
{
public async Task StartAsync(CancellationToken cancellationToken)
{
}
public async Task StopAsync(CancellationToken cancellationToken)
{
}
}
ここへさらに、アプリケーションのライフタイムの管理をしてイベントを呼び出す、IHostApplicationLifetimeを追加し、ApplicationStartedイベントを追加します。これは次で触れるように、「メソッドが制御を返すまではコンソールアプリが動作し、制御を返すと終了する」というMainメソッドのような動作をするメソッドOnStartedを作るためです。用途によってSTAThread属性を付ける事も可能になります。
public class ConsoleHostedService(IHostApplicationLifetime _AppLifetime) : IHostedService
{
public async Task StartAsync(CancellationToken cancellationToken)
{
_AppLifetime.ApplicationStarted.Register(OnStarted);
await Task.CompletedTask;
}
このOnStartedメソッドを、次のように定義して、メソッドが終わる時にライフタイムの終了処理を呼び出します。こうすることで、このOnStartedメソッドをコンソールアプリの普通のMainメソッドと同じように使えます。ここではSTAThread属性も付けています。
[STAThread]
private void OnStarted()
{
//~メインの処理~
_AppLifetime.StopApplication();
}
これでメインのサービスとなるクラスが完成したので、Program.csの方に戻ってDIコンテナへサービスとして登録します。
_ = Host.CreateDefaultBuilder(args)
.ConfigureServices((context, services) =>
{
services.AddHostedService<ConsoleHostedService>();
})
.RunConsoleAsync();
これで完成です。実行すると、OnStartedの「//~メインの処理~」部分の処理をして、終了する。というコンソールアプリとして動作すると思います。
普通のコンソールアプリと同じ動きですが、Extensionsの追加もできますし、すでにProgram.csの処理以外はDIコンテナ経由で生成されている状態なので、 ILogger<T>
を受け取ったり、任意のクラスインスタンスをDIコンテナに追加して受け取ったりができる状態となっています。
全体を見たい人は、次のgitリポジトリを見ると分かりやすいと思います。
まとめ
コンソールアプリへGenericHostを手動で組み込みました。イメージしていたよりも少ないコード変更で簡単に組み込めたのではないでしょうか。
既存のプロジェクトへも同じように組み込めるはずなので、どんどんGenericHost前提の新しいインフラ的機能を導入して、開発効率を上げていきましょう!
(WPFの記事と全く同じことを言ってますが、実際そう思ってます)