Configuration & Options Pattern
ASP.NET Core は強力な構成(Configuration)システムを持っています。appsettings.json、環境変数、コマンドライン引数など、多様なソースから設定値を読み込むことができます。
また、Options Pattern を使用することで、設定値を関連するグループごとにクラスとしてまとめ、型安全に扱うことができます。
設定の優先順位 (Configuration Providers)
ASP.NET Core のデフォルトのホストビルダー(CreateBuilder)は、以下の順序で設定を読み込みます。後から読み込まれた設定が、前の設定を上書きします。
appsettings.jsonappsettings.{Environment}.json(例:appsettings.Development.json)- ユーザーシークレット (開発環境のみ)
- 環境変数
- コマンドライン引数
環境変数による上書き
コンテナ化された環境(Docker, Kubernetes)や Azure App Service などでは、環境変数を使用して設定を上書きするのが一般的です。
ASP.NET Core のデフォルトでは、階層構造をダブルアンダースコア __ で表現します。
appsettings.json:
{
"MyService": {
"TimeoutSeconds": 30,
"IsEnabled": true
}
}
環境変数:
MyService__TimeoutSeconds=60MyService__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 による検証
- 設定クラスに属性を付与します。
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;
}
- 登録時に
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>を検討してください。 ValidateDataAnnotationsとValidateOnStartを使用して、設定ミスによる実行時エラーを防ぎましょう。