新しもの好きプログラマの耳より情報ブログ

仕事でもあるプログラミングについて役に立ちそうな情報を発信していこうというブログです。役に立たなそうな情報はfacebookで。

コンソールアプリにGenericHostを入れて便利に最新機能を使う(コード例を紹介)

概要

私の記事でも何度か触れていますが、GenericHost(WebHost)はとても便利な共通インフラなので、慣れると常に使っていきたくなります。しかし、さすがにコンソールアプリでは使えない・・・と思うかもしれませんが、使えます。コンソールアプリへのGenericHostの組み込み方を紹介します。

最初に結論まとめ

次のGitリポジトリのようにすることで組み込みができます。

https://github.com/suusanex/sample_console_generic_host

説明

さっそく、組み込み方を説明していきます。

テンプレートから生成したコンソールアプリプロジェクトに、まずは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リポジトリを見ると分かりやすいと思います。

https://github.com/suusanex/sample_console_generic_host

ちなみに

こういった話については、先日のイベントで登壇して語りました。これがその時の資料です。

www.docswell.com

そういう登壇型も含めてC# Tokyoで色々イベントやっていますので、よかったらそちらも見てみてください。

csharp-tokyo.connpass.com

まとめ

コンソールアプリへGenericHostを手動で組み込みました。イメージしていたよりも少ないコード変更で簡単に組み込めたのではないでしょうか。

既存のプロジェクトへも同じように組み込めるはずなので、どんどんGenericHost前提の新しいインフラ的機能を導入して、開発効率を上げていきましょう!

WPFの記事と全く同じことを言ってますが、実際そう思ってます)