【ASP.NET Core】JSONファイルから静的に型付けされた設定情報を簡単に得る方法

## はじめに

ASP.NET Core で JSON ファイルから設定情報を得てアプリに反映する手順です。
設定を文字列として読み込むのではなく、クラスにバインドし型付けして安全に扱いたいと思います。[^1]

.NET Core SDK のバージョンは 2.1 で、 Visual Studio for Mac を使用しています。

```sh
$ dotnet --version
2.1.302
```

サンプルは以下の dotnet コマンド、もしくは Visual Srtuio で新規の Web API プロジェクトを作成したものです。

```sh
dotnet new webapi -o 'OptionsPattern'
```

参考にした URL
[ASP.NET Core のオプション パターン](https://docs.microsoft.com/ja-jp/aspnet/core/fundamentals/configuration/options?view=aspnetcore-2.2)
[ASP.NET Core の AppSetting の構成方法](https://qiita.com/TsuyoshiUshio@github/items/f45700dc2e95e7c3992a)

## 設定情報の記述とそれをバインドするクラスの作成

`appsettings.json`に自分の設定情報を追記します。

```diff:appsettings.json
  {
+   "option1": "value1_from_json",
+   "option2": -1,
+   "option3": "1637bb4c-fff4-48d9-af98-3e9d8f022ecc"
    "Logging": {
      "LogLevel": {
        "Default": "Warning"
      }
    },
    "AllowedHosts": "*"
  }
```

追記した設定情報をバインドするオプションクラスを作成します。
プロパティ名を JSON のキー名と同じにします。（大文字小文字は違っても大丈夫です。）
今回は`string`型、`int`型、`Guid`型にバインドしてみます。

```csharp:
public class MyOptions {
  public string Option1 { get; set; }
  public int Option2 { get; set; }
  public Guid Option3 { get; set; }
}
```

## 設定情報のバインドと DI コンテナへの登録

`Startup.cs`で設定情報をオプションクラスにバインドし、DI コンテナに登録します。

```csharp:Startup.cs
public class Startup {
  public Startup(IConfiguration config) {
    // コンストラクタでコンフィグを初期化
    Configuration = config;
  }

  public IConfiguration Configuration { get; }

  public void ConfigureServices(IServiceCollection services) {
    // オプションを使用するためのサービスを追加します。
    services.AddOptions();
    // バインドするオプションクラスに対し、設定情報を登録します。
    services.Configure<MyOptions>(Configuration);

    // ※上記は services.AddMvc() より前で設定しなくても動きましたが、公式ドキュメント通り、前で設定しています。
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
  }
  // 以下略
}
```

これだけで準備完了です。

## 設定情報の利用するには

設定情報をバインドしたオプションクラスを利用するには、クラスのコンストラクタ引数に`IOptionsMonitor<TOptions>`を持つと`OptionsMonitor<TOptions>`のインスタンスが DI されます。
オプションクラスは`OptionsMonitor.CurrentValue`プロパティから得られます。

今回はコントローラークラスを作成して設定情報を利用してみたいと思います。

```csharp:OptionsController.cs
[Route("api/[controller]")]
[ApiController]
public class OptionsController: ControllerBase {

  //　コンストラクタで OptionsMonitor<TOptions> が DI される
  public OptionsController(IOptionsMonitor<MyOptions> optionsMonitor) {
    // CurrentValueプロパティでオプションクラスのインスタンスが得られる
    options = optionsMonitor.CurrentValue;
  }

  // オプションクラスのインスタンスをフィールドに保持
  private readonly MyOptions options;

  // api/options
  [HttpGet]
  public IActionResult GetOptions() {
    // JSON で返してみる
    return Ok(options);
  }
}
```

デバッグし`https://localhost:ポート番号/api/options`にブラウザでアクセスします。
以下の画像の通り設定情報がクラスにバインドされているのが確認できます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/195024/12a39eac-bfac-d6ca-3254-aeb263841df8.png)
レスポンスが返えります。(※ Chrome 拡張の Json Viewer で見やすくしています。)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/195024/3653f7b8-63f1-3130-d8e6-c15f933b2149.png)

## ネストした JSON の設定情報の読み込み

設定情報をセクションに分けて JSON に記述する場合、以下のように読み込みます。

```diff:appsettings.json
  {
    "option1": "value1_from_json",
    "option2": -1,
-   "option3": "1637bb4c-fff4-48d9-af98-3e9d8f022ecc"
+   "option3": "1637bb4c-fff4-48d9-af98-3e9d8f022ecc",
+   "subsection": {
+     "suboption1": "subvalue1_from_json",
+     "suboption2": 200,
+     "suboption3": "b2d0caf4-2aa0-4272-a175-50351282ab0f"
+   },
    "Logging": {
      "LogLevel": {
        "Default": "Warning"
      }
    },
    "AllowedHosts": "*"
  }
```

```csharp:MySubOptions.cs
// バインドするクラスを用意
public class MySubOptions {
  public string SubOption1 { get; set; }
  public int SubOption2 { get; set; }
  public Guid SubOption3 { get; set; }
}
```

```diff:Startup.csから抜粋
  public void ConfigureServices(IServiceCollection services) {
    services.AddOptions();
    services.Configure<MyOptions>(Configuration);

+   // Configuration.GetSection("セクションのキー名")　で読み込める
+   services.Configure<MySubOptions>(Configuration.GetSection("subsection"));

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
  }
```

```diff:OptionsController.cs
  [Route("api/[controller]")]
  [ApiController]
  public class OptionsController: ControllerBase {
    private readonly MyOptions options;
    private readonly MySubOptions subOptions;

+   // セクションに分けずに記述した設定と同様に扱える
-   public OptionsController(IOptionsMonitor<MyOptions> optionsMonitor) {
+   public OptionsController(
+     IOptionsMonitor<MyOptions> optionsMonitor,
+     IOptionsMonitor<MySubOptions> subOptionsMonitor
+   ) {
      options = optionsMonitor.CurrentValue;
+     subOptions = subOptionsMonitor.CurrentValue;
    }

    [HttpGet]
    public IActionResult GetOptions() {
      return Ok(options);
    }

+   [HttpGet("sub")]
+   public IActionResult GetSubOptions() {
+     return Ok(subOptions);
+   }
  }
```


ネストした設定情報がバインドされているのが確認できます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/195024/e13c5f35-38cc-5d82-3630-eb954a904adb.png)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/195024/10ac1304-507f-c8e0-fae3-23c3d54f62b4.png)


## JSON ファイルはどこで読み込まれているのか？

ASP.NET Core で JSON ファイルを読み込むには、`ConfigurationBuilder`の`AddJsonFile`拡張メソッドを呼び出します。
ただし、`Program.cs`の`CreateDefaultBuilder`メソッドで新しい`WebHostBuilder`を初期化すると、`appsettings.json`と`appsettings.{環境名}.json`は自動で読み込まれるため、今回はそちらに設定を記述しました。

```csharp:Program.csから抜粋
// ここで自動的に appsettings.json が読み込まれる
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
  WebHost.CreateDefaultBuilder(args).UseStartup<Startup>();
```

## サンプルの URL

GitHub の以下のリポジトリに今回のサンプルを置きました。
今回省略した名前空間のインポートなどを確認する場合、こちらで確認ください。
**https://github.com/sano-suguru/OptionsPattern**

[^1]: 従来の .NET Framework では`App.config`xmlファイルから`System.Configuration`名前空間の`ConfigurationSettings.AppSettings("キー名")`を使って設定情報を読み込んでいました。戻り値は文字列です。