tech.guitarrapc.cóm

Technical updates

.NET Core で Generic Host を利用する

ASP.NET Core 2.1 で追加された Generic Host (汎用ホスト) は、non-Web App アプリの作成をASP.NET Core と似た書き心地で提供します。

今後のスタンダードとなる見込みですが、どのようにして Generic Hostを利用するのか見てみましょう。

※ 社内向けブログの転載なのでシリーズ化します。

目次

TL;DR;

ここでは、.NET Core のコンソールアプリをGeneric Hostとして実装する方法を見てみましょう。

Generic Host とは

.NET Core 2.1 で追加された Generic Host (汎用ホスト)は、これからの.NET Coreにおける中心となる実装で、現在のASP.NET Core MVC で用いられている WebHostを参考に作られています。今はWebHost は Web専用、それ以外はGeneric Hostで実装ですが、今後はGeneric Hostに集約されていくロードマップです。

https://docs.microsoft.com/ja-jp/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-2.2

HostBuilder を起点にConfiguration、Logger、DI、各種機能をチェーンで追加、初期化できるのが特徴で、ホスト(アプリ) の起動と有効期間をうまく制御しようという意図が垣間見えます。

WebHost (ASP.NET Core) との違い

IHostBuilderをどのように構成するかはほぼ違いがありません。ASP.NET Core MVC では StartupクラスでConfigure したりAddするのが慣例ですが、中身はそこまで大きく違いません。

違いがでるのは、ASP.NET Core MVC がやっていた暗黙的なインフラ部分です。

ASP.NET Core MVC では、ASPNETCORE_ENVIRONMENT環境変数があると自動的に値を見てDevelopment なら Developmentに切り替え、なかったりするとProduction として扱ってくれました。

しかし、現状の Generic Hostにこの仕組みはなく、.NET Core 3.0 で入る予定のようです。

https://github.com/aspnet/AspNetCore/issues/4150

それまでは次のような処理が必要なので入ると嬉しいですね。

            .ConfigureAppConfiguration((hostContext, configApp) =>
            {
                hostContext.HostingEnvironment.EnvironmentName = System.Environment.GetEnvironmentVariable("NETCORE_ENVIRONMENT") ?? "production";
            })

Generic Host を使う目的

ASP.NET Core MVC と似たものになるように感じますが、Stack Overflow やGitHub Issueを見てるとまだこれからすそ野を広げていく感じとも感じます。特にこのあたりは共通しています。

  • Web UI と Web API を構築するプロセスの統一
  • テストの容易性を考慮したアーキテクチャ
  • 最新のクライアント側フレームワークと開発ワークフローの統合
  • 組み込まれている依存性の注入

Configure 処理の追加

Generic Host で処理を追加する場合は、Microsoft.Extensions.Xxxxx なパッケージを追加することになります。 例えば先ほどの構成なら、次のパッケージになります。

    <PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="2.2.0" />
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.2.0" />
    <PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.2.0" />

書き心地

WebHost の体験をもとにしているので、書き心地はASP.NET Core (OWIN) とほぼ同じで、定番の実装を組み込むまで一本の流れが出来上がります。

Generic Host を使わずに.NET Core ConsoleでDIする場合は次のように書いていました。*1 個人的にはかなり書き心地が悪くつらい思いをしていました。

gist.github.com

一方で、Generic Host では次のように書けます。

gist.github.com

比べてみると違いは明らかで、Generic Hostの書き心地は処理の追加方法に統一感が出たので好ましいと感じます。

先ほどの Generic Host で組んでみる例では、必要な処理を ConfigureXxxx で追加しました。つまり、そのConfigureXxxx を外せばその処理は追加されません。

特にDIベースでロガーが入るのは確かに便利で、ILoggerで隠蔽されているので、.NET あるなるだったロガーの差し替え面倒だなぁと感じるケースはかなり軽減するように感じています。逆に言うと、こういうのがいらないシンプルな実装では使う強い理由はなく、今まで通り書けばいいとも感じます。

サービス処理をDIする

Generic Host では、実際に呼び出したい処理を次のようにDIできます。

            .ConfigureServices(services =>
            {
               // サービス処理のDI
                services.AddSingleton<IBarService, BarService>();
            })

よくあるDIですが、これを書いておけば実際に処理をしたいクラス(FunctionA とします) で次のように書くと BarService がコンストラクタインジェクションされます。

    public class FunctionA
    {
        private readonly IBarService service;
        public FunctionA(IBarService service)
        {
            this.service = service;
        }

        public async Task HogeAsync()
        {
            // this.service を使って何か処理
        }
    }

まとめ

DI とかテンプレート的に構成していくことを含めて、なんとなくMicrosoft はこの書き方をオシススメテキソウな気配がします。 さくっと動かす系じゃない場合は、こっちの書き方は広まりそうかもかも?

Tips

IHostBuilder.RunConsoleAsync, IHostBuilder.Start, Host.StartAsync, IHost.RunAsync の違い

ref Stack Overflow

https://stackoverflow.com/questions/52413002/whats-the-difference-between-these-ways-to-start-run-a-generic-host-in-asp-net/52413414

ASP.NET Core 3.0ではGenericHostから構成になるかも?

ASP.NET Team のTweetによると、ASP.NET Core 3.0 では、Generic HostにConfigureWebHostDefaults で構成できるようです。

想定通りの書き方ですね。

Ref

.NET での汎用ホスト

アプリケーション停止処理の実装例

*1:まったく同じ処理ではありません