.NET Core 汎用ホスト（GenericHost）コンフィグの読み込み

# このドキュメントの内容

2019/12 に.NET Core 3.1 が正式リリースされました。2020/11 には .NET 5.0 のリリースが予定されており、そろそろ新しいインフラストラクチャへの移行を考え始めています。
手始めとして、汎用ホスト（GenericHost）関連の情報を再整理していきます。まずはコンフィグの読み込みについてまとめます。


このドキュメントの内容は .NET Core 3.1 で確認しています。

## 既定の構成ファイル（appsettings.json）

実行フォルダに appsettings.json という名前の JSON ファイルが存在する場合、HostBuilder によって自動的に読み込まれます。HostingEnvironment.EnvironmentName で表される環境変数に対応する構成ファイルがある場合にはその構成ファイルも読み込まれます。
HostingEnvironment.EnvironmentName の値が "Deveropment" であり、実行フォルダに次の二つの構成ファイルが存在する場合、同一キーの値は appsettings.Deveropment.json に記載されている値が適用されます。

```json:appsettings.json
{
  "Database": {
    "ConnectionString": "connectionString for sampleDb",
    "Name": "sampleDb"
  }
}
```

```json:appsettings.Deveropment.json
{
  "Database": {
    "ConnectionString": "connectionString for debugDb",
    "Name": "debugDb"
  }
}
```

### HostBuilderContext.Configuration

読み込まれた構成ファイルの内容は `HostBuilderContext` クラスの Configuration プロパティに格納されます。キーバリュー型のディクショナリに格納され、ネストは Database:ConnectionString のようにコロンで表されます。
このスクリーンショットでは 9 つの Provider が読み込まれていることがわかります。後述する環境変数やコマンドライン引数など様々なコンフィグが一括管理されており、サービス構成時や実行時に参照することができます。
![context.Configuration.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280969/0e7a343b-dcfb-e6b6-06aa-c93b11911bc7.png)

### 実装例

`IServiceCollection ` インターフェースの Configure メソッドで指定した型のインスタンスにバインドさせることができます。.NET Core の JSON パーサーによってバインドされます。

```c#:サービスの構成例
// using Microsoft.Extensions.Hosting;
// using Microsoft.Extensions.Options;

var builder = Host.CreateDefaultBuilder(args)
    // サービスの構成
    .ConfigureServices((HostBuilderContext context, IServiceCollection services) =>
    {
        // コンフィグを登録
        services.Configure<SampleOption>(context.Configuration);

        // JSONパーサーによってバインドできない内容の場合は独自の後処理を実装します。
        services.Configure<SampleOption>((SampleOption option) =>
        {
            // 独自の後処理
            option.Database.ConnectionString = ModifyConnectionString(option);
        });

        // 次のどちらかの方法でサービスを登録

        // 既定のバインディング
        // コンフィグのインスタンスは SampleService クラスのコンストラクタを通じて受け取ります。
        services.AddSingleton<IService, SampleService>();

        // 既定のバインディングに加えて独自の処理を組み込む必要がある場合、
        // IServiceProvider.GetRequiredService メソッドでインスタンスを取得できます。
        // 次のコードはコンフィグインスタンスの注入しか行っていませんので、結果的に上と同じ内容になります。
        services.AddSingleton<IService, SampleService>((IServiceProvider provider) =>
        {
            var option = provider.GetRequiredService<IOptions<SampleOption>>();
            return new SampleService(option);
        });

    });
```

```c#:SampleOptionクラス
public class SampleOption
{
    public SampleDbConfig Database { get; set; }
}
public class SampleDbConfig
{
    public string Name { get; set; }
    public string ConnectionString { get; set; }
}
```

```c#:SampleServiceクラス
internal interface IService
{
    void Execute();
}
internal class SampleService : IService
{
    // サービスの定義に従って IServiceCollection に登録されたコンフィグのインスタンスが注入されます。
    public SampleService(IOptions<SampleOption> options)
    {
    }
}
```

## 任意の構成ファイル

appsettings.json 以外の JSON 構成ファイルを使用する場合、`IConfigurationBuilder` インターフェースの AddJsonFile メソッドで読み込みます。appsettings.json に同じキーが存在する場合、後から読み込まれたこちらのファイルの値が適用されます。

```c#:サービスの構成例
// using Microsoft.Extensions.Hosting;
// using Microsoft.Extensions.Options;

var builder = Host.CreateDefaultBuilder(args)
    // コンフィグの構成
    .ConfigureAppConfiguration((HostBuilderContext context, IConfigurationBuilder builder) =>
    {
        // 実行フォルダの MyAppSettings.json を構成に追加します。
        // ファイルが存在しない可能性がある場合は optional に true を指定します。
        builder.AddJsonFile($"MyAppSettings.json"
            , optional: true
            , reloadOnChange: true
        );
        builder.AddJsonFile($"MyAppSettings.{context.HostingEnvironment.EnvironmentName}.json"
            , optional: true
            , reloadOnChange: true
        );
    })
    // サービスの構成（前述している内容と同じですので割愛しています）
    .ConfigureServices((HostBuilderContext context, IServiceCollection services) =>
    {
        // コンフィグを登録
        services.Configure<SampleOption>(context.Configuration);
        // サービスを登録
        services.AddSingleton<IService, SampleService>();
    });
```

## 環境変数

環境変数を用いて構成ファイルの値を上書きするには、キーを一致させます。

環境変数は Host.CreateDefaultBuilder メソッドの既定の動作で読み込まれます（「既定の構成ファイル」の項のスクリーンショットの context.Configuration.Providers[3]）。構成ファイルに同じキーが存在する場合、後から読み込まれたほうの値が適用されます。

アプリケーション固有のキーの値をバインドさせるには、キー名にそのアプリケーションを表すプレフィクスを付加し、`IConfigurationBuilder` インターフェースの AddEnvironmentVariables メソッドで読み込むのが便利です。なお、AddEnvironmentVariables メソッドで読み込んだ環境変数は新しく context.Configuration.Providers に追加されます。

.NET Core の既定の環境変数はプレフィクス DOTNET_ が付加されたキーで定義されています。
例えば、キー DOTNET_ENVIRONMENT の値は HostingEnvironment.EnvironmentName プロパティの値にバインドされ、前述の構成ファイルの読み込みなどに使用されています。

```c#:サービスの構成例
// using Microsoft.Extensions.Hosting;
// using Microsoft.Extensions.Options;

var builder = Host.CreateDefaultBuilder(args)
    // コンフィグの構成
    .ConfigureAppConfiguration((HostBuilderContext context, IConfigurationBuilder builder) =>
    {
        // 指定したプレフィクス（この例では "SAMPLEAPP_"）で始まる環境変数を構成に追加します。
        // プレフィクスが除かれたキーで読み込まれます。
        builder.AddEnvironmentVariables(prefix: "SAMPLEAPP_");
    })
    // サービスの構成（前述している内容と同じですので割愛しています）
    .ConfigureServices((HostBuilderContext context, IServiceCollection services) =>
    {
        // コンフィグを登録
        services.Configure<SampleOption>(context.Configuration);
        // サービスを登録
        services.AddSingleton<IService, SampleService>();
    });
```

VisualStudio のデバッグ設定に次のように設定されている場合、"Database:ConnectionString" には前述した構成ファイルの値ではなくこの環境変数の値が適用されます。"Database:Name" は設定されていませんので構成ファイルの値が適用されます。
![環境変数.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280969/63edc187-f844-aa18-1ec6-929173405a36.png)


## コマンドライン引数

コマンドライン引数を用いて構成ファイルの値を上書きする場合も、キーを一致させます。

コマンドライン引数は Host.CreateDefaultBuilder メソッドの既定の動作で読み込まれます（「既定の構成ファイル」の項のスクリーンショットの context.Configuration.Providers[4]）。構成ファイルや環境変数に同じキーが存在する場合、後から読み込まれたほうの値が適用されます。

追加の構成ファイルや環境変数を読み込む場合でコマンドライン引数の値を最も優先するには、最後に `IConfigurationBuilder` インターフェースの AddCommandLine メソッドで読み込む必要があります。

```c#:サービスの構成例
// using Microsoft.Extensions.Hosting;
// using Microsoft.Extensions.Options;

var builder = Host.CreateDefaultBuilder(args)
    // コンフィグの構成
    .ConfigureAppConfiguration((HostBuilderContext context, IConfigurationBuilder builder) =>
    {
        // 追加の構成ファイル
        builder.AddJsonFile($"MyAppSettings.json", optional: true);
        // 追加の環境変数
        builder.AddEnvironmentVariables(prefix: "SAMPLEAPP_");
        // 最後にコマンドライン引数を読み込んで上書き
        builder.AddCommandLine(args);
    })
    // サービスの構成（前述している内容と同じですので割愛しています）
    .ConfigureServices((HostBuilderContext context, IServiceCollection services) =>
    {
        // コンフィグを登録
        services.Configure<SampleOption>(context.Configuration);
        // サービスを登録
        services.AddSingleton<IService, SampleService>();
    });
```

コマンドライン引数は「キー=値」の形式で指定します。"Database:ConnectionString" には前述した構成ファイルの値ではなくこのコマンドライン引数の値が適用されます。"Database:Name" は設定されていませんので構成ファイルの値が適用されます。

```console:コマンドライン引数を指定して起動
SampleApp.exe Database:ConnectionString=connectionString
```

## まとめ

構成ファイル・環境変数・コマンドラインによるコンフィグを統合し、特定の型のインスタンスにバインドすることができます。実行環境に依存する値や呼び出し時に指定する値を整理し、適切な箇所で設定するように設計しましょう。