C#入門編です。今回はC#における「Host(ホスト)」について解説します。

設定管理、DI、ロギング…それぞれは理解できたけど、毎回組み立てるのが大変」と感じていませんか?

本記事は以下の方に役立つ内容となっています。

  • 設定・DI・ロギングを統合的に扱いたい方
  • HostApplicationBuilderの使い方を学びたい方
  • モダンなC#アプリの構成パターンを身につけたい方

Hostは、これまで学んできた設定管理・DI・ロギングを「一つの基盤」として統合する仕組みです。

今回は、C#モダン開発における構成要素の最後のピース「Host」を学び、これまでの知識を総まとめします。

  • 設定管理:appsettings.jsonとIConfigurationで設定を外部化
  • DI(依存性注入):依存関係を外から渡す設計手法
  • ログ:ILoggerによる構造化ログ
  • Host:設定・DI・ログを統合するアプリの骨格(←今回紹介!)
プロ太

Hostを使うと、これまで手動で組み立てていた「設定読み込み → DIコンテナ構築 → ロギング設定」を、簡潔に記述できます!

以下の設定管理・DI・ロギングに関する記事をあらかじめ見ておいてもらえると、より理解が深まるかと思います。

C#入門編(23)モダンなC#の設定管理を徹底解説 ~appsettings.jsonとIConfigurationの使い方~ C#入門編です。今回はC#におけるモダンな設定管理の方法について解説します。 API の URL やアプリの動作モードなど、環境...
C#入門編(24)依存性注入(DI)を徹底解説 ~DIの本質からDIコンテナの使い方まで~ C#入門編です。今回はC#における依存性注入(DI: Dependency Injection)について解説します。 「依存性注...
C#入門編(25)ロギングの基本 ~ILoggerの使い方とSerilogでファイル出力する方法~ C#入門編です。今回はC#におけるロギング(ログ出力)について解説します。 「Console.WriteLineでデバッグしてる...

演習コードをGitHubで公開してます。

動画も作成しています。

https://www.youtube.com/watch?v=kgx1UpXTDNI

講義1:Hostとは

なぜHostは必要か

これまでの入門編で、以下の技術を個別に学んできました。

  • 設定管理IConfigurationで複数の設定ソースから設定値読み込み
  • DIServiceCollectionでDIコンテナを構築する
  • ロギングILoggerLoggerFactoryでログを出力する

これまで学んできたように、設定ファイル、DI、ロギングを行うコードは以下のようなりますが、これらは定型的なコードであり毎回手動で書くのは手間がかかります。

// 設定の読み込み
IConfigurationRoot configuration = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json") //設定ファイル
    .AddCommandLine(args)  //実行時引数
    .Build();

// DIコンテナの設定
ServiceCollection services = new ServiceCollection();
services.AddLogging(builder =>
{
    builder.AddConsole();
});
services.AddSingleton<IConfiguration>(configuration);
services.AddTransient<MyService>();

using ServiceProvider provider = services.BuildServiceProvider();
provider.GetRequiredService<MyService>().Run();

加えて、実際のアプリ開発では、これに加えて以下も必要です。

  • 環境(Development/Production)に応じて設定を切り替える
  • アプリのライフサイクル(起動・終了)を管理する

このような定型的なコードを毎回ゼロから書くのは非効率ですし、書き方がプロジェクトごとにバラバラになりがちです。

プロ美

たしかに、設定・DI・ロギングの初期化コードって、どのプロジェクトでも似たようなことを書いてる気がする…

Hostが提供するもの

Host(ホスト) は、.NETアプリの「骨格」を提供する仕組みです。以下の機能が統合されています。

機能説明
設定管理appsettings.jsonや環境変数を自動読み込み
DIサービスの登録と解決を行うDIコンテナ
ロギングILoggerを使ったログ出力基盤
実行環境切り替えProduction/Development等の切り替え
ライフサイクル管理アプリの起動・終了を制御

Hostを使うと、これらの初期化が数行で完了し、すぐにアプリのロジックに集中できます。

さきほど例示したコード相当は以下のように短く書けます。(正確には、よく使う標準的な構成を自動で用意してくれています。)

// Hostを使った場合
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddTransient<MyService>();
using IHost host = builder.Build();
// ↑ appsettings.json読み込み、コンソールロギング、DIコンテナが全部セットアップ済み!

host.Services.GetRequiredService<MyService>().Run();

たったこれだけで、設定・DI・ロギングがすべて使える状態になります。

プロ太

それでは、次にもう少し詳しくこの「HostApplicationBuilder」と「IHost」の使い方をみていきましょう。

講義2:HostApplicationBuilder、IHostの使い方

HostApplicationBuilderの基本

Host.CreateApplicationBuilder()で取得できるHostApplicationBuilderには、以下のプロパティがあり、DIコンテナへの登録・ロギングの設定などを行えます。

プロパティ用途
ServicesIServiceCollectionDIコンテナへのサービス登録
ConfigurationConfigurationManager
(IConfigurationを実装)
設定の読み込み・追加
EnvironmentIHostEnvironment実行環境の情報
LoggingILoggingBuilderロギングの設定

HostApplicationBuilderでは、設定の読み込み、ロギング、DIコンテナへのサービス登録について、標準的なセットが事前に用意されています。

設定の読み込み

基本

Host.CreateApplicationBuilder()は、以下のアプリの設定ソースを自動で読み込みます。

順序設定ソース補足
1 (優先度は最低)appsettings.json共通設定
2appsettings.{Environment}.json環境別設定(例:appsettings.Development.json)
3ユーザシークレットDevelopment環境のみ。APIキーなど機密情報用
4環境変数
5 (優先度は最高)コマンドライン引数

例えば、以下のようなappsettings.jsonを配置しておくと、

{
  "ApiSettings": {
    "BaseUrl": "https://api.example.com",
    "TimeoutSeconds": 30
  }
}

以下のようにプログラムで設定値を読み込むことができます。

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

// 設定値を取得(自動で読み込まれている)
string? baseUrl = builder.Configuration["ApiSettings:BaseUrl"];
Console.WriteLine($"API URL: {baseUrl}"); // "https://api.example.com"を出力

また、DIコンテナへ自動でIConfigurationが登録されるため、以下のように各サービスクラスで参照することもできます。

public class MyService
{
    private readonly IConfiguration _configuration;

    public MyService(IConfiguration configuration)
    {
        _configuration = configuration;
        //  _configurationを使って設定値を読み込み可能
    }
プロ美

「設定値読み込み」の標準的な構成の作成とDIコンテナへの登録を、自動でやってくれるんだね!

環境の切り替え

実行環境(Development/Production等)は、環境変数「DOTNET_ENVIRONMENT」で指定します。例えば、PowerShellなら以下です。

$env:DOTNET_ENVIRONMENT = "Development"

例えば「Development」を指定すると、「appsettings.json」に加えて「appsettings.Development.json」も読み込まれ、同じキーがあれば上書きされます。

プログラム内で現在の環境を確認するには、「builder.Environment」を使います。

Console.WriteLine(builder.Environment.EnvironmentName); 
プロ太

Visual Studioでは、プロジェクトのプロパティ→デバッグ→環境変数で設定できます。launchSettings.jsonに保存されます。

ロギングの設定

Host.CreateApplicationBuilder()は、以下のログプロバイダーを自動で登録します。

プロバイダー説明
Consoleコンソールへの出力
Debugデバッグ出力
(Visual Studioの出力ウィンドウなど)
EventSource診断・監視ツールから購読するための出力先(通常は画面には表示されない)

つまり特に何も設定しなくても、ILogger<T>を使ったログ出力が「コンソール、デバッグ出力」に表示されます。

また、ロギング機能を使うためのDIコンテナへの各種登録も自動で行われるため、以下のようにサービスクラスでロギング機能を利用可能です。

public class MyService
{
    private readonly ILogger<MyService> _logger;

    public MyService(ILogger<MyService> logger)
    {
        _logger = logger;
    }

    public void DoWork()
    {
        _logger.LogInformation("処理を実行しました");  // コンソールに出力される
    }
}
プロ美

設定値と同じで、ロギングについても標準的な構成の作成とDIコンテナへの登録を自動で行ってくれるんだね!

HostApplicationBuilderにおける設定自動読み込み・自動ロギング設定についての詳細はMicrosoft Learnの記事も参考にしてください。

ライフサイクル管理 ~IHostedServiceの使い方~

Hostの重要な役割の1つとして、アプリのライフサイクル管理もあります。

IHostedServiceインターフェースを実装したサービスを登録すると、Hostが起動・終了を管理してくれます。

例えばBackgroundServiceは、IHostedServiceを実装したバックグランド実行アプリ向けの基底クラスで、以下のように使います。

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
// ↓IHostedServiceとして登録(Singletonとして登録され、Hostが起動・終了を管理)
builder.Services.AddHostedService<MyService>();

IHost host = builder.Build();

await host.RunAsync();


public class MyService : BackgroundService
{
    private readonly ILogger<MyService> _logger;

    public MyService(ILogger<MyService> logger)
    {
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("サービス開始");

        // 何らかの処理(ここでは3秒待機)
        await Task.Delay(3000, stoppingToken);

        _logger.LogInformation("サービス終了");
    }
}

これを実行すると以下のように出力されます。このプログラムはExecuteAsyncが完了しても、Ctrl+Cが押されるまで待機し続けます。

info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: MyService[0]
      サービス開始
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
      Content root path: D:\data\projects\00apps\ConsoleApp1\ConsoleApp1\bin\Debug\net10.0
info: MyService[0]
      サービス終了

(「Microsoft.Hosting.Lifetime」となっているログはHostが出力したものです。)

host.RunAsync()を呼ぶと、Hostは以下を行います。

  1. 登録されたIHostedServiceをすべて起動
  2. Ctrl+Cが押されるまで待機(サービス側で全体を終了させることも可能)
  3. シャットダウン時にIHostedServiceを安全に停止(グレースフルシャットダウン)
プロ太

CancellationTokenを使うと、Ctrl+Cが押されたときに処理を中断可能です。これがHostによる「グレースフルシャットダウン」の仕組みです。

プロ美

なるほど!Hostを使うと「起動・実行・終了」をHostが面倒見てくれるんだね。

補足:ASP.NET CoreやMAUIにおけるHost

今回学んだHost.CreateApplicationBuilder()は、汎用のHostを作るためのものです。

実は、ASP.NET CoreやMAUIにもそれぞれに特化したビルダーがあり、基本的な使い方は共通しています。

ビルダー用途
Host.CreateApplicationBuilder()汎用
WebApplication.CreateBuilder()ASP.NET Core Webアプリ
MauiApp.CreateBuilder().NET MAUIアプリ

いずれもbuilder.ServicesでDI登録、builder.Configurationで設定取得…という同じパターンです。

プロ太

今回学んだHostの知識は、ASP.NET CoreやMAUIでもそのまま活かせます。

「設定・DI・ロギングの統合基盤」という考え方は.NETアプリ共通のパターンなのです。

演習:設定・DI・ロギングをHostで統合しよう

これまで学んできた設定管理・DI・ロギングを、Hostで統合してみましょう。前回のDataServiceとApiClientを使った演習コードを追加・修正し、以下の構成を実現します。

  • 設定:appsettings.jsonからAPI接続先を読み込む
  • DI:ApiClient、DataServiceをDIコンテナに登録
  • ロギング:Serilogでコンソールとファイルに出力

最終的には以下の構成になります。

プロ太

それでは前回演習コードをベースに作成していきましょう!

手順1:パッケージ追加

以下のパッケージを追加します。

  • Microsoft.Extensions.Hosting

手順2:設定ファイルと設定用クラスを追加

以下のappsettings.jsonを追加します。

{
  "ApiSettings": {
    "BaseUrl": "https://api.example.com",
    "TimeoutSeconds": 15
  }
}

また、このApiSettingsに対応するクラス(ApiSettings.cs)も追加します。(Optionパターンにより設定値をマッピングします)

namespace DISample;

public class ApiSettings
{
    // デフォルト値は設定漏れ時のフォールバック
    public string BaseUrl { get; set; } = "";
    public int TimeoutSeconds { get; set; } = 30;
}

また、appsettings.jsonが実行時に参照できるようにするため、プロジェクトファイルへ以下のように<ItemGroup>を追記しましょう。

<Project Sdk="Microsoft.NET.Sdk">

  ...

  <ItemGroup>
    <None Update="appsettings.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
  </ItemGroup>

</Project>
プロ美

C#入門編(23):モダンなC#の設定管理」の演習でも、この設定はしていたね!

手順3:Programを修正

以下のように、Program.csを修正します。Hostを使って、ログ設定、設定読み込み、DIコンテナへのサービス登録を行っています。

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Serilog;
using DISample;

// 0. Serilogの設定
// 出力先、フォーマット、ログレベルなどを指定
Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Information()
    //↓ Hosting.LifetimeのログレベルをWarningに上げて冗長な情報を抑制
    .MinimumLevel.Override("Microsoft.Hosting.Lifetime", Serilog.Events.LogEventLevel.Warning)
    .Enrich.WithMachineName()
    .WriteTo.Console(outputTemplate:
        "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}")
    .WriteTo.File("logs/app-.log",
        rollingInterval: RollingInterval.Day,
        outputTemplate:
        "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] [Machine: {MachineName}] {Message:lj}{NewLine}{Exception}")
    .CreateLogger();

// 1. HostApplicationBuilderを作成
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

// 2. Serilogを使用するように設定(既存のLog.Logger設定を使用)
builder.Logging.ClearProviders(); // 既存のロギングプロバイダーをクリア
builder.Logging.AddSerilog(dispose: true);

// 3. 設定をバインド(appsettings.jsonから自動読み込み)
// これでIOptions<ApiSettings>がDIコンテナに登録される
builder.Services.Configure<ApiSettings>(
    builder.Configuration.GetSection("ApiSettings"));

// 4. サービスを登録
// ここでMockApiClientに変更すれば、実装を簡単に差し替えられる
builder.Services.AddTransient<IApiClient, ApiClient>();

// 5. DataServiceをHostedServiceとして登録
builder.Services.AddHostedService<DataService>();

// 6. ホストをビルドして実行
IHost host = builder.Build();

await host.RunAsync();
プロ太

Hostが裏で色々な設定を行ってくれているので、コンポジションルート(Program.cs)のコードはこのように比較的シンプルになります。

「3.設定をバインド」では、Optionパターンを使っています。これを使うと、設定ソースの値をクラスへバインドし、DIで各サービスへ注入可能になります。

Optionパターンでは、設定値のバリデーションや設定値の再読み込みなど様々な機能があります。詳しくはMicrosoftの記事を参考にしてください。

手順4:ApiClient、DataServiceクラスを修正

ApiClient.csを以下のように修正します。

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace DISample;

public class ApiClient : IApiClient
{
    private readonly ApiSettings _settings;
    private readonly ILogger<ApiClient> _logger;

    public ApiClient(IOptions<ApiSettings> options, ILogger<ApiClient> logger)
    {
        _settings = options.Value;
        _logger = logger;
    }

    public string GetData()
    {
        _logger.LogDebug("APIリクエスト開始: {BaseUrl}", _settings.BaseUrl);
        
        // 実際のアプリではHttpClientを使ってAPI呼び出し
        return $"[{_settings.BaseUrl}] からデータ取得(タイムアウト: {_settings.TimeoutSeconds}秒)";
    }
}

コンストラクタの「IOptions<ApiSettings> options」で、DIコンテナへ登録された設定値クラスが注入されます。

DataService.csを以下のように修正します。DataServiceはBackgroundServiceを継承させて、IHostedServiceとして振舞えるようにします。

using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace DISample;

public class DataService : BackgroundService
{
    private readonly IApiClient _client;
    private readonly ILogger<DataService> _logger;

    public DataService(IApiClient client, ILogger<DataService> logger)
    {
        _client = client;
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("データ取得処理を実行します");

        var data = _client.GetData();
        _logger.LogDebug("APIからのレスポンス: {Response}", data);

        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 取得結果: {data}");

        _logger.LogInformation("データ取得処理が正常に完了しました");

        await Task.CompletedTask;
    }
}
プロ美

builder.Services.AddHostedService<DataService>()」って、コンポジションルートで登録されていたね。

だから、host.Run()でDataServiceの「ExecuteAsync」が実行されるんだ!

アプリを実行

アプリを実行すると以下のように出力され、終了を待機する状態になり、そのあとCtrl+Cで終了します。(ログがファイルとしても出力されています)

[12:40:05 INF] データ取得処理を実行します
[12:40:05] 取得結果: [https://api.example.com] からデータ取得(タイムアウト: 15秒)
[12:40:05 INF] データ取得処理が正常に完了しました
プロ美

Hostを使ったアプリが動いた!これで、設定・DI・ロギングが備わったC#アプリ開発の骨組みはばっちりだね!

プロ太

そうですね。これがC#のモダンなアプリ開発の土台です!

環境種別(Production/Development)変更による設定ファイルの切り替えなども試してみると、より理解が深まるかと思います。

ちなみに、DataServiceのExecuteAsyncが終了したらアプリ全体を終了させたい場合は、以下のようにIHostApplicationLifetimeを使います。

public class DataService : BackgroundService
{
    ...
    private readonly IHostApplicationLifetime _lifetime;

    public DataService(..., IHostApplicationLifetime lifetime)
    {
        ...
        _lifetime = lifetime;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        ...
        _lifetime.StopApplication(); //アプリ全体を終了
        await Task.CompletedTask;
    }
}

IHostApplicationLifetimeはデフォルトでDIコンテナに登録されています。

まとめ

今回はHostについて学びました。

Hostとは、設定管理・DI・ロギング・ライフサイクル管理を統合する「アプリの骨格」です。これまで手動で組み立てていた初期化の定型コードを大きく削減できます。

BackgroundServiceIHostedService)を使うと、Hostがサービスのライフサイクル(起動・終了)を管理してくれます。

演習では、これまで学んできた設定管理・DI・ロギングをHostで統合し、ライフサイクル管理についてはBackgroundServiceを使った実践的なアプリを作成しました。

プロ太

これで、モダンC#アプリの基盤となる4つの柱「設定管理・DI・ロギング・Host」をすべて学び終えました!

引き続き、一緒にC#プログラミングについて学んでいきましょう。

ABOUT ME
プロ太
ソフトウェア開発を楽しく、効率的に行う方法を追求しています。 開発者の視点から技術的課題に向き合い、「純粋な技術的興味に基づく探求」と「実践的な課題解決」という二つの柱を両輪として活動しています。