C# におけるオプションパターン
[[academy-video-youtube({"vid": "ko1Ie9gDydY", "start_time": "0", "title": "The Options Pattern in C#", "creator": "Tim Corey", "length": "10m 15s"})]]
.NET アプリケーションでの構成は通常、appsettings.json、環境変数、またはユーザーシークレットに含まれています。 構成データをファイルから取り出してクラスにクリーンでテスト可能な方法で取り込むことが、オプションパターンの解決策です。 JSON キーを手動で読み出したり、生の文字列を他の場所に渡したりする代わりに、構成セクションを強く型付けされた C# クラスにバインドし、依存性注入を使って必要な場所に提供します。
Tim Corey は、彼のビデオ"The Options Pattern in C#"で、オプションパターンの3つのバリエーション(IOptionsMonitor)を紹介し、Blazorサーバーアプリでそれぞれをデモし、構成が欠落または不正な場合にアプリケーションがすぐに失敗するようにバリデーションを追加する方法を示します。 構成ファイルから読み込み、依存性注入を使用する .NET プロジェクトを作成する際、このパターンは基礎的なものです。
セットアップ: POCO モデルと appsettings.json
[0:34 - 1:46] Tim はすでに2つの部分が設定済みの Blazor Web アプリ(サーバー側でレンダリング、クライアント側のインタラクティビティなし)から始めます。 最初のセクションは、appsettings.json内のCloudInfoセクションにある3つのキーと値のペアです。
// appsettings.json
{
"CloudInfo": {
"Storage": "https://storage.example.com",
"Website": "https://www.example.com",
"API": "https://api.example.com"
}
}// appsettings.json
{
"CloudInfo": {
"Storage": "https://storage.example.com",
"Website": "https://www.example.com",
"API": "https://api.example.com"
}
}次の部分は、JSON のキーを反映したプロパティを持つプレーン C# クラス(POCO)です。
public class CloudInfoOptions
{
public string Storage { get; set; }
public string Website { get; set; }
public string API { get; set; }
}public class CloudInfoOptions
{
public string Storage { get; set; }
public string Website { get; set; }
public string API { get; set; }
}Tim は、構成ソースが特定のappsettings.jsonである必要がないことを指摘します。 それはsecrets.json、またはその組み合わせである可能性があります。 オプションパターンは、アプリケーションに登録されているどの構成プロバイダーからも読み取り、POCO のプロパティ名は規約により JSON キーに一致します。
Program.cs でオプションを登録する
[2:25 - 3:15] 構成を依存性注入に配線するためには、Program.csで2つのメソッドを呼び出す必要があります。
// Register CloudInfoOptions bound to the "CloudInfo" section
builder.Services.AddOptions<CloudInfoOptions>()
.BindConfiguration("CloudInfo");// Register CloudInfoOptions bound to the "CloudInfo" section
builder.Services.AddOptions<CloudInfoOptions>()
.BindConfiguration("CloudInfo");AddOptions<t>() はタイプをDIに登録します。 BindConfiguration("CloudInfo") は構成のどのセクションをマップするかをフレームワークに伝えます。文字列appsettings.json内のJSONキーに対応しています。 セクション名とクラス名が一致しない場合、文字列引数は、フレームワークが適切なデータを見つけるために使用するものです。
IOptions: シングルトンアプローチ
[3:15 - 5:18] 登録が完了したら、TimはBlazorページにIOptions<CloudInfoOptions>を使用して構成を注入します。
@inject IOptions<CloudInfoOptions> CloudConfig
@code {
protected override void OnInitialized()
{
var options = CloudConfig.Value;
// options.Storage, options.Website, options.API are available
}
}@inject IOptions<CloudInfoOptions> CloudConfig
@code {
protected override void OnInitialized()
{
var options = CloudConfig.Value;
// options.Storage, options.Website, options.API are available
}
}重要な点はIOptions<t>がシングルトンとして登録されていることです。 構成値はアプリケーションの開始時に一度読み込まれ、プロセスのライフタイム期間中キャッシュされます。 アプリが実行中にIOptionsはその変更を反映しません。
ほとんどのアプリケーションでは、これが正しい選択です。構成は実行時にはほとんど変更されず、シングルトンのライフタイムはリクエストごとのオーバーヘッドがないことを意味します。
IOptionsSnapshot: スコープ付きリローディング
[5:18 - 6:55] Tim はIOptionsSnapshotに置き換え、スコープ付きバリエーションをデモします。
@inject IOptionsSnapshot<CloudInfoOptions> CloudConfig@inject IOptionsSnapshot<CloudInfoOptions> CloudConfigIOptionsSnapshot<t> は各リクエストごとに新しい構成を読み込む(スコープ付きライフタイム)。 アプリが実行中にappsettings.jsonを変更した場合、次のWebリクエストで新しい値が取得されます。 前のリクエストは自身のスナップショットを保持するため、中途半端な一貫性はありません。
これは、機能フラグの切り替え、API エンドポイントの更新、ストレージ接続文字列のローテーションなど、アプリケーションで変更の効果を再起動せずに反映する必要がある場合に役立ちます。 落とし穴は、構成を再読み込みするコストがリクエストごとに少し増えますが、ほとんどの作業負荷では無視できるレベルです。
IOptionsMonitor: リアルタイムの変更通知
[6:55 - 8:34] 第三の変形IOptionsMonitor<t>は、スナップショットリロードを超えるものです。 それは構成の変更を積極的に監視し、値が更新されたときにコールバックをトリガーできます。
@inject IOptionsMonitor<CloudInfoOptions> CloudConfig
@code {
protected override void OnInitialized()
{
// Current values
var current = CloudConfig.CurrentValue;
// Register a callback for live changes
CloudConfig.OnChange(updatedOptions =>
{
// React to configuration changes in real time
});
}
}@inject IOptionsMonitor<CloudInfoOptions> CloudConfig
@code {
protected override void OnInitialized()
{
// Current values
var current = CloudConfig.CurrentValue;
// Register a callback for live changes
CloudConfig.OnChange(updatedOptions =>
{
// React to configuration changes in real time
});
}
}リクエスト間でのみリフレッシュするIOptionsMonitorは長時間実行される操作中に変化を検出できます。 Tim はこれが主にバックグラウンドサービスや SignalR ハブ、単一の HTTP リクエストよりも長いライフタイムを持つコンポーネントに最も関連することを指摘しています。
検証の追加
[8:34 - 10:05] Tim がカバーする最終部分は 検証 です。 オプションパターンは、構成が初めてアクセスされたときに実行されるインライン検証ルールをサポートします。
builder.Services.AddOptions<CloudInfoOptions>()
.BindConfiguration("CloudInfo")
.Validate(opts =>
!string.IsNullOrEmpty(opts.Storage) &&
!string.IsNullOrEmpty(opts.Website) &&
!string.IsNullOrEmpty(opts.API));builder.Services.AddOptions<CloudInfoOptions>()
.BindConfiguration("CloudInfo")
.Validate(opts =>
!string.IsNullOrEmpty(opts.Storage) &&
!string.IsNullOrEmpty(opts.Website) &&
!string.IsNullOrEmpty(opts.API));Tim はappsettings.jsonから削除してアプリケーションを起動することで、失敗ケースをデモします。 結果は即座に処理されない例外です:"CloudInfoOptions の検証に失敗しました。" 構成が不完全な状態ではアプリケーションの起動を拒否しますが、これはまさに望ましい動作です。 2 AM にプロダクションでヌル参照をヒットするよりも、スタートアップで欠落した値を発見する方がはるかに優れています。
より複雑なバリデーションシナリオ(クロスプロパティチェック、条件付きルール)には、複数のIValidateOptions<t>を実装することができます。
まとめ: 三つのインタフェース、一つのパターン
[10:05 - 10:10] オプションパターンは構成ストレージと消費の間にクリーンな分離を提供します。 IOptionsは、構成が静的であるほとんどのユースケースをカバーします。 IOptionsSnapshotは、リクエスト間で変更を取得する必要があるアプリケーションを処理します。 IOptionsMonitorは、構成の更新にリアルタイムで対応する必要がある長時間実行されるコンポーネントに役立ちます。 そして、検証は必要な値が不足している場合にアプリケーションが大音量で失敗することを保証します。
結論
[10:10 - 10:15] 要約すると、.Validate()を追加してください。
パターンはすべての .NET プロジェクトタイプで機能します:Blazor、ASP.NET Core、コンソールアプリ、ワーカーサービス。 登録はどこでも同じです。
ヒントの例: どのインターフェースを使用するか不明な場合は、IOptions<t>で始めてください。 シンプルで、リクエストごとのオーバーヘッドがなく、スタートアップ時に設定されて変更されない構成の大多数のケースをカバーします。 具体的に実行時リロードが必要になった場合にのみ、IOptionsMonitorに移行します。

