メインコンテンツまでスキップ

Configuration & Options Pattern

ASP.NET Core は強力な構成(Configuration)システムを持っています。appsettings.json、環境変数、コマンドライン引数など、多様なソースから設定値を読み込むことができます。 また、Options Pattern を使用することで、設定値を関連するグループごとにクラスとしてまとめ、型安全に扱うことができます。

設定の優先順位 (Configuration Providers)

ASP.NET Core のデフォルトのホストビルダー(CreateBuilder)は、以下の順序で設定を読み込みます。後から読み込まれた設定が、前の設定を上書きします。

  1. appsettings.json
  2. appsettings.{Environment}.json (例: appsettings.Development.json)
  3. ユーザーシークレット (開発環境のみ)
  4. 環境変数
  5. コマンドライン引数

環境変数による上書き

コンテナ化された環境(Docker, Kubernetes)や Azure App Service などでは、環境変数を使用して設定を上書きするのが一般的です。 ASP.NET Core のデフォルトでは、階層構造をダブルアンダースコア __ で表現します。

appsettings.json:

{
"MyService": {
"TimeoutSeconds": 30,
"IsEnabled": true
}
}

環境変数:

  • MyService__TimeoutSeconds=60
  • MyService__IsEnabled=false

これにより、コードを変更することなく環境ごとに設定を変更できます。

Options Pattern (オプションパターン)

設定値をコード内で直接 IConfiguration["MyService:TimeoutSeconds"] のようにアクセスするのは推奨されません。キーのタイプミスや型変換の手間が発生するためです。 代わりに、設定値を POCO (Plain Old CLR Object) クラスにバインドして使用します。

1. 設定クラスの作成

public class MyServiceOptions
{
public const string SectionName = "MyService";

public int TimeoutSeconds { get; set; }
public bool IsEnabled { get; set; }
public string ApiKey { get; set; } = string.Empty;
}

2. DIコンテナへの登録

Program.cs で設定セクションをクラスにバインドします。

builder.Services.Configure<MyServiceOptions>(
builder.Configuration.GetSection(MyServiceOptions.SectionName));

3. サービスでの利用 (IOptions インターフェース)

DI を通じて設定クラスを受け取ります。利用シーンに応じて3つのインターフェースがあります。

IOptions<T>

  • シングルトンとして登録されます。
  • アプリケーション起動時に一度だけ値を読み込みます。
  • 設定ファイルの変更を検知しません。
public class MyService
{
private readonly MyServiceOptions _options;

public MyService(IOptions<MyServiceOptions> options)
{
// .Value でインスタンスにアクセス
_options = options.Value;
}
}

IOptionsSnapshot<T>

  • スコープド (Scoped) として登録されます。
  • リクエストごとに再計算されます。
  • 設定ファイルの変更をリクエスト単位で検知できます。
  • シングルトンサービスには注入できません。
public class MyScopedService
{
private readonly MyServiceOptions _options;

public MyScopedService(IOptionsSnapshot<MyServiceOptions> options)
{
_options = options.Value;
}
}

IOptionsMonitor<T>

  • シングルトンとして登録されます。
  • 設定変更をリアルタイムで検知できます。
  • CurrentValue プロパティで現在の値を取得したり、OnChange イベントで変更時の処理を記述できます。
public class MyMonitorService
{
private readonly IOptionsMonitor<MyServiceOptions> _optionsMonitor;

public MyMonitorService(IOptionsMonitor<MyServiceOptions> optionsMonitor)
{
_optionsMonitor = optionsMonitor;

// 変更検知時のリスナー登録
_optionsMonitor.OnChange(newOptions => {
Console.WriteLine($"Config changed: {newOptions.TimeoutSeconds}");
});
}

public void DoWork()
{
// 常に最新の値を取得
var currentOptions = _optionsMonitor.CurrentValue;
}
}

使い分けのベストプラクティス

インターフェースライフサイクル設定変更の検知推奨ユースケース
IOptions<T>Singleton×基本的にこれを使用。設定が変わらない場合。
IOptionsSnapshot<T>Scoped○ (リクエスト毎)設定変更を反映したいが、リクエスト処理中は一貫性を保ちたい場合。
IOptionsMonitor<T>Singleton○ (リアルタイム)シングルトンサービスで設定変更を検知したい場合。

バリデーション付き設定

設定値が不正なままアプリケーションが起動すると、実行時にエラーが発生します。起動時に設定値を検証することで、問題を早期に発見できます。

Data Annotations による検証

  1. 設定クラスに属性を付与します。
using System.ComponentModel.DataAnnotations;

public class MyServiceOptions
{
public const string SectionName = "MyService";

[Range(1, 60)]
public int TimeoutSeconds { get; set; }

[Required]
public string ApiKey { get; set; } = string.Empty;
}
  1. 登録時に ValidateDataAnnotations()ValidateOnStart() を呼び出します。
builder.Services.AddOptions<MyServiceOptions>()
.Bind(builder.Configuration.GetSection(MyServiceOptions.SectionName))
.ValidateDataAnnotations()
.ValidateOnStart(); // 起動時にチェックを行い、失敗すれば例外をスローして起動を停止

これにより、ApiKey が設定されていない場合や TimeoutSeconds が範囲外の場合、アプリケーション起動時に明確なエラーメッセージと共に停止します。

まとめ

  • 設定値は appsettings.json や環境変数から読み込まれ、環境変数が優先されます。
  • 生の IConfiguration ではなく、Options Pattern を使用して型安全に設定を扱ってください。
  • 通常は IOptions<T> を使用し、動的な更新が必要な場合は IOptionsSnapshot<T>IOptionsMonitor<T> を検討してください。
  • ValidateDataAnnotationsValidateOnStart を使用して、設定ミスによる実行時エラーを防ぎましょう。