ASP.NET Core 2.1で追加されたGeneric Host (汎用ホスト) は、non-Web Appアプリの作成をASP.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で入る予定のようです。
それまでは次のような処理が必要なので入ると嬉しいですね。
.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 個人的にはかなり書き心地が悪くつらい思いをしていました。
https://gist.github.com/guitarrapc/426753ed981708d7211301be67e3f09c
一方で、Generic Hostでは次のように書けます。
https://gist.github.com/guitarrapc/31d58318160192f5ad9aba6bd56fbaa0
比べてみると違いは明らかで、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
ASP.NET Core 3.0ではGenericHostから構成になるかも?
ASP.NET Teamのツイートによると、ASP.NET Core 3.0では、Generic HostにConfigureWebHostDefaultsで構成できるようです。
David Fowler (@davidfowl) January 18, 2019
想定通りの書き方ですね。
Ref
*1:まったく同じ処理ではありません