ASP.NET Core のオプション パターン
Note
これは、この記事の最新バージョンではありません。 現在のリリースについては、この記事の .NET 8 バージョンを参照してください。
警告
このバージョンの ASP.NET Core はサポート対象から除外されました。 詳細については、「.NET および .NET Core サポート ポリシー」を参照してください。 現在のリリースについては、この記事の .NET 8 バージョンを参照してください。
重要
この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft はここに示されている情報について、明示か黙示かを問わず、一切保証しません。
現在のリリースについては、この記事の .NET 8 バージョンを参照してください。
作成者: Rick Anderson。
オプション パターンは、クラスを使用して、関連する設定のグループに、厳密に型指定されたアクセスを提供します。 構成設定がシナリオ別に個々のクラスに分離されるとき、アプリは次の 2 つの重要なソフトウェア エンジニアリング原則に従います。
構成データを検証するメカニズムもオプションによって提供されます。 詳しくは、「オプションの検証」セクションをご覧ください。
この記事では、ASP.NET Core でのオプションのパターンについて説明します。 コンソール アプリでオプションのパターンを使用する方法の詳細については、「.NET でのオプションのパターン」を参照してください。
階層的な構成をバインドする
関連する構成値を読み取る方法としては、オプション パターンを使用することをお勧めします。 たとえば、以下の構成値を読み取るには、次のようにします:
"Position": {
"Title": "Editor",
"Name": "Joe Smith"
}
次の PositionOptions
クラスを作成します:
public class PositionOptions
{
public const string Position = "Position";
public string Title { get; set; } = String.Empty;
public string Name { get; set; } = String.Empty;
}
オプション クラス:
- 非抽象である必要があります。
- config の対応する項目がバインドされている型のパブリックの読み取り/書き込みプロパティがあります。
- 構成内の一致するエントリにバインドされたそれ自体の読み取り/書き込みプロパティがあります。
- フィールドがバインドされて "いません"。 上のコード
Position
はバインドされません。Position
フィールドは、クラスを構成プロバイダーにバインドするときに、アプリで文字列"Position"
をハードコーディングする必要をなくすために使用されます。
コード例を次に示します。
- ConfigurationBinder.Bind を呼び出して、
PositionOptions
クラスをPosition
セクションにバインドします。 Position
構成データを表示します。
public class Test22Model : PageModel
{
private readonly IConfiguration Configuration;
public Test22Model(IConfiguration configuration)
{
Configuration = configuration;
}
public ContentResult OnGet()
{
var positionOptions = new PositionOptions();
Configuration.GetSection(PositionOptions.Position).Bind(positionOptions);
return Content($"Title: {positionOptions.Title} \n" +
$"Name: {positionOptions.Name}");
}
}
上のコードでは、アプリが開始された後の JSON 構成ファイルへの変更が既定で読み取られます。
ConfigurationBinder.Get<T>
指定された型をバインドして返します。 ConfigurationBinder.Get<T>
は ConfigurationBinder.Bind
を使用するよりも便利な場合があります。 次のコードは、PositionOptions
クラスで ConfigurationBinder.Get<T>
を使用する方法を示しています:
public class Test21Model : PageModel
{
private readonly IConfiguration Configuration;
public PositionOptions? positionOptions { get; private set; }
public Test21Model(IConfiguration configuration)
{
Configuration = configuration;
}
public ContentResult OnGet()
{
positionOptions = Configuration.GetSection(PositionOptions.Position)
.Get<PositionOptions>();
return Content($"Title: {positionOptions.Title} \n" +
$"Name: {positionOptions.Name}");
}
}
上のコードでは、アプリが開始された後の JSON 構成ファイルへの変更が既定で読み取られます。
Bind では、抽象クラスの具体化も可能です。 抽象クラスである SomethingWithAName
を使用する次のコードを検討します。
namespace ConfigSample.Options;
public abstract class SomethingWithAName
{
public abstract string? Name { get; set; }
}
public class NameTitleOptions(int age) : SomethingWithAName
{
public const string NameTitle = "NameTitle";
public override string? Name { get; set; }
public string Title { get; set; } = string.Empty;
public int Age { get; set; } = age;
}
次のコードは、NameTitleOptions
構成の値を表示します。
public class Test33Model : PageModel
{
private readonly IConfiguration Configuration;
public Test33Model(IConfiguration configuration)
{
Configuration = configuration;
}
public ContentResult OnGet()
{
var nameTitleOptions = new NameTitleOptions(22);
Configuration.GetSection(NameTitleOptions.NameTitle).Bind(nameTitleOptions);
return Content($"Title: {nameTitleOptions.Title} \n" +
$"Name: {nameTitleOptions.Name} \n" +
$"Age: {nameTitleOptions.Age}"
);
}
}
Bind
の呼び出しは、Get<>
の呼び出しほどは厳密ではありません。
Bind
では、abstract の具体化が可能です。Get<>
ではインスタンス自体を作成する必要があります。
オプション パターン
"オプション パターン" を使用するときのもう 1 つの方法は、Position
セクションをバインドし、それを依存関係挿入サービス コンテナーに追加することです。 次のコードでは、PositionOptions
は Configure でサービスコンテナーに追加され、構成にバインドされます:
using ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
var app = builder.Build();
下記のコードは、上記のコードを使用して位置オプションを読み取ります:
public class Test2Model : PageModel
{
private readonly PositionOptions _options;
public Test2Model(IOptions<PositionOptions> options)
{
_options = options.Value;
}
public ContentResult OnGet()
{
return Content($"Title: {_options.Title} \n" +
$"Name: {_options.Name}");
}
}
上のコードでは、アプリが開始された後の JSON 構成ファイルへの変更は読み取られ "ません"。 アプリの開始後に変更を読み取るには、IOptionsSnapshot を使用します。
オプションのインターフェイス
- 要求ごとにオプションを再計算する必要があるシナリオで役立ちます。 詳細については、「IOptionsSnapshot を使用して更新データを読み取る」を参照してください。
- スコープとして登録されているため、シングルトン サービスには挿入できません。
- 名前付きオプションをサポートします。
- オプションを取得し、
TOptions
インスタンスのオプション通知を管理するために使用されます。 - シングルトンとして登録されており、任意のサービスの有効期間に挿入できます。
- サポートするものは次のとおりです。
- 変更通知
- 名前付きオプション
- 再読み込み可能な構成
- 選択的なオプションの無効化 (IOptionsMonitorCache<TOptions>)
ポスト構成のシナリオでは、すべての IConfigureOptions<TOptions> 構成が行われた後で、オプションを設定または変更できます。
IOptionsFactory<TOptions> は、新しいオプション インスタンスを作成します。 Create メソッドが 1 つ含まれています。 既定の実装では、登録されている IConfigureOptions<TOptions> と IPostConfigureOptions<TOptions> がすべて受け取られ、先にすべての構成を実行し、その後、ポスト構成を実行します。 IConfigureNamedOptions<TOptions> と IConfigureOptions<TOptions> が区別され、適切なインターフェイスのみが呼び出されます。
IOptionsMonitorCache<TOptions> は IOptionsMonitor<TOptions> によって使用され、TOptions
インスタンスをキャッシュします。 IOptionsMonitorCache<TOptions> は、値が再計算されるよう、モニターのオプション インスタンスを無効にします (TryRemove)。 TryAdd を利用し、手動で値を入力できます。 Clear メソッドは、すべての名前付きインスタンスをオンデマンドで再作成するときに使用されます。
IOptionsSnapshot を使用して更新データを読み取る
- オプションは、要求の有効期間中にアクセスされ、キャッシュされたとき、要求につき 1 回計算されます。
- これは、スコープ付きサービスであり、要求ごとに再計算されるため、パフォーマンスの大幅な低下が発生する可能性があります。 詳細については、この GitHub のイシューと構成バインドのパフォーマンスの向上に関する記事を参照してください。
- 更新された構成値の読み取りをサポートする構成プロバイダーを使用しているとき、構成の変更は、アプリの開始後に読み取られます。
IOptionsMonitor
と IOptionsSnapshot
の違いは次のとおりです。
IOptionsMonitor
は常に最新のオプション値を取得するシングルトン サービスです。これは、シングルトンの依存関係で特に便利です。IOptionsSnapshot
はスコープ サービスであり、IOptionsSnapshot<T>
オブジェクトの構築時にオプションのスナップショットを提供します。 オプションのスナップショットは、一時的な依存関係およびスコープのある依存関係で使用されるように設計されています。
次のコードでは IOptionsSnapshot<TOptions> を使用します。
public class TestSnapModel : PageModel
{
private readonly MyOptions _snapshotOptions;
public TestSnapModel(IOptionsSnapshot<MyOptions> snapshotOptionsAccessor)
{
_snapshotOptions = snapshotOptionsAccessor.Value;
}
public ContentResult OnGet()
{
return Content($"Option1: {_snapshotOptions.Option1} \n" +
$"Option2: {_snapshotOptions.Option2}");
}
}
次のコードは、MyOptions
のバインド先となる構成インスタンスを登録します。
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));
var app = builder.Build();
上記のコードでは、アプリが開始された後の JSON 構成ファイルへの変更が読み取られます。
IOptionsMonitor
次のコードは、MyOptions
のバインド先となる構成インスタンスを登録します。
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));
var app = builder.Build();
IOptionsMonitor<TOptions> の使用例を次に示します。
public class TestMonitorModel : PageModel
{
private readonly IOptionsMonitor<MyOptions> _optionsDelegate;
public TestMonitorModel(IOptionsMonitor<MyOptions> optionsDelegate )
{
_optionsDelegate = optionsDelegate;
}
public ContentResult OnGet()
{
return Content($"Option1: {_optionsDelegate.CurrentValue.Option1} \n" +
$"Option2: {_optionsDelegate.CurrentValue.Option2}");
}
}
上のコードでは、アプリが開始された後の JSON 構成ファイルへの変更が既定で読み取られます。
IConfigureNamedOptions を使用した名前付きオプションのサポート
名前付きオプション:
- 複数の構成セクションが同じプロパティにバインドされている場合に便利です。
- 大文字と小文字の区別があります。
次の appsettings.json
ファイルを考えてみます。
{
"TopItem": {
"Month": {
"Name": "Green Widget",
"Model": "GW46"
},
"Year": {
"Name": "Orange Gadget",
"Model": "OG35"
}
}
}
TopItem:Month
と TopItem:Year
をバインドする 2 つのクラスを作成するのではなく、各セクションに対して次のクラスを使用します。
public class TopItemSettings
{
public const string Month = "Month";
public const string Year = "Year";
public string Name { get; set; } = string.Empty;
public string Model { get; set; } = string.Empty;
}
次のコードは、名前付きオプションを構成します。
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<TopItemSettings>(TopItemSettings.Month,
builder.Configuration.GetSection("TopItem:Month"));
builder.Services.Configure<TopItemSettings>(TopItemSettings.Year,
builder.Configuration.GetSection("TopItem:Year"));
var app = builder.Build();
次のコードは、名前付きオプションを表示します。
public class TestNOModel : PageModel
{
private readonly TopItemSettings _monthTopItem;
private readonly TopItemSettings _yearTopItem;
public TestNOModel(IOptionsSnapshot<TopItemSettings> namedOptionsAccessor)
{
_monthTopItem = namedOptionsAccessor.Get(TopItemSettings.Month);
_yearTopItem = namedOptionsAccessor.Get(TopItemSettings.Year);
}
public ContentResult OnGet()
{
return Content($"Month:Name {_monthTopItem.Name} \n" +
$"Month:Model {_monthTopItem.Model} \n\n" +
$"Year:Name {_yearTopItem.Name} \n" +
$"Year:Model {_yearTopItem.Model} \n" );
}
}
すべてのオプションが名前付きインスタンスです。 IConfigureOptions<TOptions> インスタンスは、string.Empty
である、Options.DefaultName
インスタンスを対象とするものとして処理されます。 IConfigureNamedOptions<TOptions> はまた、IConfigureOptions<TOptions> を実装します。 IOptionsFactory<TOptions> の既定の実装には、それぞれを適切に使用するロジックがあります。 名前付きオプション null
は、特定の名前付きインスタンスではなく、すべての名前付きインスタンスを対象とするために使用されます。 ConfigureAll と PostConfigureAll では、この規則が使用されます。
OptionsBuilder API
OptionsBuilder<TOptions> は、TOptions
インスタンスの構成に使用されます。 OptionsBuilder
は名前付きオプションの作成を簡略化します。これは最初の AddOptions<TOptions>(string optionsName)
の呼び出しに対する 1 つのパラメーターにすぎず、後続のすべての呼び出しが表示されなくなるためです。 サービスの依存関係を受け入れるオプションの検証と ConfigureOptions
のオーバーロードは、OptionsBuilder
を介してのみ可能です。
OptionsBuilder
は、「オプションの検証」セクションで使用されます。
カスタム リポジトリの追加の詳細については、「AddOptions を使用してカスタム リポジトリを構成する」を参照してください。
DI サービスを使用してオプションを構成する
オプションの構成中、次の 2 つの方法で依存関係挿入からサービスにアクセスできます。
OptionsBuilder<TOptions> の Configure に構成デリゲートを渡します。
OptionsBuilder<TOptions>
から Configure のオーバーロードが与えられます。これにより、最大 5 つのサービスを使用してオプションを構成できます。builder.Services.AddOptions<MyOptions>("optionalName") .Configure<Service1, Service2, Service3, Service4, Service5>( (o, s, s2, s3, s4, s5) => o.Property = DoSomethingWith(s, s2, s3, s4, s5));
IConfigureOptions<TOptions> または IConfigureNamedOptions<TOptions> を実装する型を作成し、その型をサービスとして登録します。
サービスの作成は複雑なため、Configure に構成デリゲートを渡す方法をおすすめします。 型を作成することは、Configure を呼び出すときにフレームワークが行うことと同じです。 Configure を呼び出すと、一時的な汎用の IConfigureNamedOptions<TOptions> が登録されます。これには、指定された汎用サービスの型を受け入れるコンストラクターが含まれています。
オプションの検証
オプションの検証により、オプションの値を検証できます。
次の appsettings.json
ファイルを考えてみます。
{
"MyConfig": {
"Key1": "My Key One",
"Key2": 10,
"Key3": 32
}
}
次のクラスは "MyConfig"
構成セクションにバインドする目的で使用され、いくつかの DataAnnotations
規則が適用されます。
public class MyConfigOptions
{
public const string MyConfig = "MyConfig";
[RegularExpression(@"^[a-zA-Z''-'\s]{1,40}$")]
public string Key1 { get; set; }
[Range(0, 1000,
ErrorMessage = "Value for {0} must be between {1} and {2}.")]
public int Key2 { get; set; }
public int Key3 { get; set; }
}
コード例を次に示します。
- AddOptions を呼び出して、
MyConfigOptions
クラスにバインドする OptionsBuilder<TOptions> を取得します。 - ValidateDataAnnotations を呼び出し、
DataAnnotations
を利用した検証を有効にします。
using OptionsValidationSample.Configuration;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
.ValidateDataAnnotations();
var app = builder.Build();
ValidateDataAnnotations
拡張メソッドは Microsoft.Extensions.Options.DataAnnotations NuGet パッケージに定義されています。 Microsoft.NET.Sdk.Web
SDK を使用する Web アプリの場合、このパッケージは共有フレームワークから暗黙的に参照されます。
次のコードは、構成値または検証エラーを表示します。
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly IOptions<MyConfigOptions> _config;
public HomeController(IOptions<MyConfigOptions> config,
ILogger<HomeController> logger)
{
_config = config;
_logger = logger;
try
{
var configValue = _config.Value;
}
catch (OptionsValidationException ex)
{
foreach (var failure in ex.Failures)
{
_logger.LogError(failure);
}
}
}
public ContentResult Index()
{
string msg;
try
{
msg = $"Key1: {_config.Value.Key1} \n" +
$"Key2: {_config.Value.Key2} \n" +
$"Key3: {_config.Value.Key3}";
}
catch (OptionsValidationException optValEx)
{
return Content(optValEx.Message);
}
return Content(msg);
}
次のコードは、デリゲートを使用して、より複雑な検証規則を適用します。
using OptionsValidationSample.Configuration;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
.ValidateDataAnnotations()
.Validate(config =>
{
if (config.Key2 != 0)
{
return config.Key3 > config.Key2;
}
return true;
}, "Key3 must be > than Key2."); // Failure message.
var app = builder.Build();
IValidateOptions<TOptions>
および IValidatableObject
次のクラスは、IValidateOptions<TOptions> を実装します。
public class MyConfigValidation : IValidateOptions<MyConfigOptions>
{
public MyConfigOptions _config { get; private set; }
public MyConfigValidation(IConfiguration config)
{
_config = config.GetSection(MyConfigOptions.MyConfig)
.Get<MyConfigOptions>();
}
public ValidateOptionsResult Validate(string name, MyConfigOptions options)
{
string? vor = null;
var rx = new Regex(@"^[a-zA-Z''-'\s]{1,40}$");
var match = rx.Match(options.Key1!);
if (string.IsNullOrEmpty(match.Value))
{
vor = $"{options.Key1} doesn't match RegEx \n";
}
if ( options.Key2 < 0 || options.Key2 > 1000)
{
vor = $"{options.Key2} doesn't match Range 0 - 1000 \n";
}
if (_config.Key2 != default)
{
if(_config.Key3 <= _config.Key2)
{
vor += "Key3 must be > than Key2.";
}
}
if (vor != null)
{
return ValidateOptionsResult.Fail(vor);
}
return ValidateOptionsResult.Success;
}
}
IValidateOptions
は、Program.cs
からクラスに検証コードを移動できるようにします。
前のコードを使用すると、次のコードにより Program.cs
で検証が有効になります。
using Microsoft.Extensions.Options;
using OptionsValidationSample.Configuration;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.Configure<MyConfigOptions>(builder.Configuration.GetSection(
MyConfigOptions.MyConfig));
builder.Services.AddSingleton<IValidateOptions
<MyConfigOptions>, MyConfigValidation>();
var app = builder.Build();
オプションの検証では IValidatableObject もサポートされています。 クラス自体に含まれるクラスのクラス レベルの検証を実行するには:
IValidatableObject
インターフェイスとその Validate メソッドをクラス内で実装します。Program.cs
内で ValidateDataAnnotations を呼び出します。
ValidateOnStart
オプションの検証は、IOptions<TOptions>、IOptionsSnapshot<TOptions>、または IOptionsMonitor<TOptions> の実装が作成されるときに初めて実行されます。 オプションの検証を能動的に実行するには、アプリの開始時に Program.cs
で ValidateOnStart を呼び出します。
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
.ValidateDataAnnotations()
.ValidateOnStart();
オプションのポスト構成
ポスト構成を IPostConfigureOptions<TOptions> を使用して設定します。 ポスト構成は、すべての IConfigureOptions<TOptions> 構成が行われた後で実行されます。
using OptionsValidationSample.Configuration;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig));
builder.Services.PostConfigure<MyConfigOptions>(myOptions =>
{
myOptions.Key1 = "post_configured_key1_value";
});
PostConfigure は、名前付きオプションのポスト構成に使用できます。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<TopItemSettings>(TopItemSettings.Month,
builder.Configuration.GetSection("TopItem:Month"));
builder.Services.Configure<TopItemSettings>(TopItemSettings.Year,
builder.Configuration.GetSection("TopItem:Year"));
builder.Services.PostConfigure<TopItemSettings>("Month", myOptions =>
{
myOptions.Name = "post_configured_name_value";
myOptions.Model = "post_configured_model_value";
});
var app = builder.Build();
すべての構成インスタンスをポスト構成するには、PostConfigureAll を使用します。
using OptionsValidationSample.Configuration;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig));
builder.Services.PostConfigureAll<MyConfigOptions>(myOptions =>
{
myOptions.Key1 = "post_configured_key1_value";
});
Program.cs
のアクセスオプション
Program.cs
で IOptions<TOptions> または IOptionsMonitor<TOptions> にアクセスするには、WebApplication.Services で GetRequiredService を呼び出します。
var app = builder.Build();
var option1 = app.Services.GetRequiredService<IOptionsMonitor<MyOptions>>()
.CurrentValue.Option1;
その他のリソース
- サンプル コードを表示またはダウンロードします (ダウンロード方法)。
作成者: Kirk Larkin および Rick Anderson
オプション パターンは、クラスを使用して、関連する設定のグループに、厳密に型指定されたアクセスを提供します。 構成設定がシナリオ別に個々のクラスに分離されるとき、アプリは次の 2 つの重要なソフトウェア エンジニアリング原則に従います。
構成データを検証するメカニズムもオプションによって提供されます。 詳しくは、「オプションの検証」セクションをご覧ください。
この記事では、ASP.NET Core でのオプションのパターンについて説明します。 コンソール アプリでオプションのパターンを使用する方法の詳細については、「.NET でのオプションのパターン」を参照してください。
階層的な構成をバインドする
関連する構成値を読み取る方法としては、オプション パターンを使用することをお勧めします。 たとえば、以下の構成値を読み取るには、次のようにします:
"Position": {
"Title": "Editor",
"Name": "Joe Smith"
}
次の PositionOptions
クラスを作成します:
public class PositionOptions
{
public const string Position = "Position";
public string Title { get; set; } = String.Empty;
public string Name { get; set; } = String.Empty;
}
オプション クラス:
- パラメーターのないパブリック コンストラクターを持った非抽象でなければなりません。
- 型のパブリックな読み取り/書き込みプロパティは、すべてバインドされます。
- フィールドはバインド "されません"。 上のコード
Position
はバインドされません。Position
フィールドは、クラスを構成プロバイダーにバインドするときに、アプリで文字列"Position"
をハードコーディングする必要をなくすために使用されます。
コード例を次に示します。
- ConfigurationBinder.Bind を呼び出して、
PositionOptions
クラスをPosition
セクションにバインドします。 Position
構成データを表示します。
public class Test22Model : PageModel
{
private readonly IConfiguration Configuration;
public Test22Model(IConfiguration configuration)
{
Configuration = configuration;
}
public ContentResult OnGet()
{
var positionOptions = new PositionOptions();
Configuration.GetSection(PositionOptions.Position).Bind(positionOptions);
return Content($"Title: {positionOptions.Title} \n" +
$"Name: {positionOptions.Name}");
}
}
上のコードでは、アプリが開始された後の JSON 構成ファイルへの変更が既定で読み取られます。
ConfigurationBinder.Get<T>
指定された型をバインドして返します。 ConfigurationBinder.Get<T>
は ConfigurationBinder.Bind
を使用するよりも便利な場合があります。 次のコードは、PositionOptions
クラスで ConfigurationBinder.Get<T>
を使用する方法を示しています:
public class Test21Model : PageModel
{
private readonly IConfiguration Configuration;
public PositionOptions? positionOptions { get; private set; }
public Test21Model(IConfiguration configuration)
{
Configuration = configuration;
}
public ContentResult OnGet()
{
positionOptions = Configuration.GetSection(PositionOptions.Position)
.Get<PositionOptions>();
return Content($"Title: {positionOptions.Title} \n" +
$"Name: {positionOptions.Name}");
}
}
上のコードでは、アプリが開始された後の JSON 構成ファイルへの変更が既定で読み取られます。
"オプション パターン" を使用するときのもう 1 つの方法は、Position
セクションをバインドし、それを依存関係挿入サービス コンテナーに追加することです。 次のコードでは、PositionOptions
は Configure でサービスコンテナーに追加され、構成にバインドされます:
using ConfigSample.Options;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<PositionOptions>(
builder.Configuration.GetSection(PositionOptions.Position));
var app = builder.Build();
下記のコードは、上記のコードを使用して位置オプションを読み取ります:
public class Test2Model : PageModel
{
private readonly PositionOptions _options;
public Test2Model(IOptions<PositionOptions> options)
{
_options = options.Value;
}
public ContentResult OnGet()
{
return Content($"Title: {_options.Title} \n" +
$"Name: {_options.Name}");
}
}
上のコードでは、アプリが開始された後の JSON 構成ファイルへの変更は読み取られ "ません"。 アプリの開始後に変更を読み取るには、IOptionsSnapshot を使用します。
オプションのインターフェイス
- 要求ごとにオプションを再計算する必要があるシナリオで役立ちます。 詳細については、「IOptionsSnapshot を使用して更新データを読み取る」を参照してください。
- スコープとして登録されているため、シングルトン サービスには挿入できません。
- 名前付きオプションをサポートします。
- オプションを取得し、
TOptions
インスタンスのオプション通知を管理するために使用されます。 - シングルトンとして登録されており、任意のサービスの有効期間に挿入できます。
- サポートするものは次のとおりです。
- 変更通知
- 名前付きオプション
- 再読み込み可能な構成
- 選択的なオプションの無効化 (IOptionsMonitorCache<TOptions>)
ポスト構成のシナリオでは、すべての IConfigureOptions<TOptions> 構成が行われた後で、オプションを設定または変更できます。
IOptionsFactory<TOptions> は、新しいオプション インスタンスを作成します。 Create メソッドが 1 つ含まれています。 既定の実装では、登録されている IConfigureOptions<TOptions> と IPostConfigureOptions<TOptions> がすべて受け取られ、先にすべての構成を実行し、その後、ポスト構成を実行します。 IConfigureNamedOptions<TOptions> と IConfigureOptions<TOptions> が区別され、適切なインターフェイスのみが呼び出されます。
IOptionsMonitorCache<TOptions> は IOptionsMonitor<TOptions> によって使用され、TOptions
インスタンスをキャッシュします。 IOptionsMonitorCache<TOptions> は、値が再計算されるよう、モニターのオプション インスタンスを無効にします (TryRemove)。 TryAdd を利用し、手動で値を入力できます。 Clear メソッドは、すべての名前付きインスタンスをオンデマンドで再作成するときに使用されます。
IOptionsSnapshot を使用して更新データを読み取る
- オプションは、要求の有効期間中にアクセスされ、キャッシュされたとき、要求につき 1 回計算されます。
- これは、スコープ付きサービスであり、要求ごとに再計算されるため、パフォーマンスの大幅な低下が発生する可能性があります。 詳細については、この GitHub のイシューと構成バインドのパフォーマンスの向上に関する記事を参照してください。
- 更新された構成値の読み取りをサポートする構成プロバイダーを使用しているとき、構成の変更は、アプリの開始後に読み取られます。
IOptionsMonitor
と IOptionsSnapshot
の違いは次のとおりです。
IOptionsMonitor
は常に最新のオプション値を取得するシングルトン サービスです。これは、シングルトンの依存関係で特に便利です。IOptionsSnapshot
はスコープ サービスであり、IOptionsSnapshot<T>
オブジェクトの構築時にオプションのスナップショットを提供します。 オプションのスナップショットは、一時的な依存関係およびスコープのある依存関係で使用されるように設計されています。
次のコードでは IOptionsSnapshot<TOptions> を使用します。
public class TestSnapModel : PageModel
{
private readonly MyOptions _snapshotOptions;
public TestSnapModel(IOptionsSnapshot<MyOptions> snapshotOptionsAccessor)
{
_snapshotOptions = snapshotOptionsAccessor.Value;
}
public ContentResult OnGet()
{
return Content($"Option1: {_snapshotOptions.Option1} \n" +
$"Option2: {_snapshotOptions.Option2}");
}
}
次のコードは、MyOptions
のバインド先となる構成インスタンスを登録します。
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));
var app = builder.Build();
上記のコードでは、アプリが開始された後の JSON 構成ファイルへの変更が読み取られます。
IOptionsMonitor
次のコードは、MyOptions
のバインド先となる構成インスタンスを登録します。
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<MyOptions>(
builder.Configuration.GetSection("MyOptions"));
var app = builder.Build();
IOptionsMonitor<TOptions> の使用例を次に示します。
public class TestMonitorModel : PageModel
{
private readonly IOptionsMonitor<MyOptions> _optionsDelegate;
public TestMonitorModel(IOptionsMonitor<MyOptions> optionsDelegate )
{
_optionsDelegate = optionsDelegate;
}
public ContentResult OnGet()
{
return Content($"Option1: {_optionsDelegate.CurrentValue.Option1} \n" +
$"Option2: {_optionsDelegate.CurrentValue.Option2}");
}
}
上のコードでは、アプリが開始された後の JSON 構成ファイルへの変更が既定で読み取られます。
IConfigureNamedOptions を使用した名前付きオプションのサポート
名前付きオプション:
- 複数の構成セクションが同じプロパティにバインドされている場合に便利です。
- 大文字と小文字の区別があります。
次の appsettings.json
ファイルを考えてみます。
{
"TopItem": {
"Month": {
"Name": "Green Widget",
"Model": "GW46"
},
"Year": {
"Name": "Orange Gadget",
"Model": "OG35"
}
}
}
TopItem:Month
と TopItem:Year
をバインドする 2 つのクラスを作成するのではなく、各セクションに対して次のクラスを使用します。
public class TopItemSettings
{
public const string Month = "Month";
public const string Year = "Year";
public string Name { get; set; } = string.Empty;
public string Model { get; set; } = string.Empty;
}
次のコードは、名前付きオプションを構成します。
using SampleApp.Models;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<TopItemSettings>(TopItemSettings.Month,
builder.Configuration.GetSection("TopItem:Month"));
builder.Services.Configure<TopItemSettings>(TopItemSettings.Year,
builder.Configuration.GetSection("TopItem:Year"));
var app = builder.Build();
次のコードは、名前付きオプションを表示します。
public class TestNOModel : PageModel
{
private readonly TopItemSettings _monthTopItem;
private readonly TopItemSettings _yearTopItem;
public TestNOModel(IOptionsSnapshot<TopItemSettings> namedOptionsAccessor)
{
_monthTopItem = namedOptionsAccessor.Get(TopItemSettings.Month);
_yearTopItem = namedOptionsAccessor.Get(TopItemSettings.Year);
}
public ContentResult OnGet()
{
return Content($"Month:Name {_monthTopItem.Name} \n" +
$"Month:Model {_monthTopItem.Model} \n\n" +
$"Year:Name {_yearTopItem.Name} \n" +
$"Year:Model {_yearTopItem.Model} \n" );
}
}
すべてのオプションが名前付きインスタンスです。 IConfigureOptions<TOptions> インスタンスは、string.Empty
である、Options.DefaultName
インスタンスを対象とするものとして処理されます。 IConfigureNamedOptions<TOptions> はまた、IConfigureOptions<TOptions> を実装します。 IOptionsFactory<TOptions> の既定の実装には、それぞれを適切に使用するロジックがあります。 名前付きオプション null
は、特定の名前付きインスタンスではなく、すべての名前付きインスタンスを対象とするために使用されます。 ConfigureAll と PostConfigureAll では、この規則が使用されます。
OptionsBuilder API
OptionsBuilder<TOptions> は、TOptions
インスタンスの構成に使用されます。 OptionsBuilder
は名前付きオプションの作成を簡略化します。これは最初の AddOptions<TOptions>(string optionsName)
の呼び出しに対する 1 つのパラメーターにすぎず、後続のすべての呼び出しが表示されなくなるためです。 サービスの依存関係を受け入れるオプションの検証と ConfigureOptions
のオーバーロードは、OptionsBuilder
を介してのみ可能です。
OptionsBuilder
は、「オプションの検証」セクションで使用されます。
カスタム リポジトリの追加の詳細については、「AddOptions を使用してカスタム リポジトリを構成する」を参照してください。
DI サービスを使用してオプションを構成する
オプションの構成中、次の 2 つの方法で依存関係挿入からサービスにアクセスできます。
OptionsBuilder<TOptions> の Configure に構成デリゲートを渡します。
OptionsBuilder<TOptions>
から Configure のオーバーロードが与えられます。これにより、最大 5 つのサービスを使用してオプションを構成できます。builder.Services.AddOptions<MyOptions>("optionalName") .Configure<Service1, Service2, Service3, Service4, Service5>( (o, s, s2, s3, s4, s5) => o.Property = DoSomethingWith(s, s2, s3, s4, s5));
IConfigureOptions<TOptions> または IConfigureNamedOptions<TOptions> を実装する型を作成し、その型をサービスとして登録します。
サービスの作成は複雑なため、Configure に構成デリゲートを渡す方法をおすすめします。 型を作成することは、Configure を呼び出すときにフレームワークが行うことと同じです。 Configure を呼び出すと、一時的な汎用の IConfigureNamedOptions<TOptions> が登録されます。これには、指定された汎用サービスの型を受け入れるコンストラクターが含まれています。
オプションの検証
オプションの検証により、オプションの値を検証できます。
次の appsettings.json
ファイルを考えてみます。
{
"MyConfig": {
"Key1": "My Key One",
"Key2": 10,
"Key3": 32
}
}
次のクラスは "MyConfig"
構成セクションにバインドする目的で使用され、いくつかの DataAnnotations
規則が適用されます。
public class MyConfigOptions
{
public const string MyConfig = "MyConfig";
[RegularExpression(@"^[a-zA-Z''-'\s]{1,40}$")]
public string Key1 { get; set; }
[Range(0, 1000,
ErrorMessage = "Value for {0} must be between {1} and {2}.")]
public int Key2 { get; set; }
public int Key3 { get; set; }
}
コード例を次に示します。
- AddOptions を呼び出して、
MyConfigOptions
クラスにバインドする OptionsBuilder<TOptions> を取得します。 - ValidateDataAnnotations を呼び出し、
DataAnnotations
を利用した検証を有効にします。
using OptionsValidationSample.Configuration;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
.ValidateDataAnnotations();
var app = builder.Build();
ValidateDataAnnotations
拡張メソッドは Microsoft.Extensions.Options.DataAnnotations NuGet パッケージに定義されています。 Microsoft.NET.Sdk.Web
SDK を使用する Web アプリの場合、このパッケージは共有フレームワークから暗黙的に参照されます。
次のコードは、構成値または検証エラーを表示します。
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly IOptions<MyConfigOptions> _config;
public HomeController(IOptions<MyConfigOptions> config,
ILogger<HomeController> logger)
{
_config = config;
_logger = logger;
try
{
var configValue = _config.Value;
}
catch (OptionsValidationException ex)
{
foreach (var failure in ex.Failures)
{
_logger.LogError(failure);
}
}
}
public ContentResult Index()
{
string msg;
try
{
msg = $"Key1: {_config.Value.Key1} \n" +
$"Key2: {_config.Value.Key2} \n" +
$"Key3: {_config.Value.Key3}";
}
catch (OptionsValidationException optValEx)
{
return Content(optValEx.Message);
}
return Content(msg);
}
次のコードは、デリゲートを使用して、より複雑な検証規則を適用します。
using OptionsValidationSample.Configuration;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
.ValidateDataAnnotations()
.Validate(config =>
{
if (config.Key2 != 0)
{
return config.Key3 > config.Key2;
}
return true;
}, "Key3 must be > than Key2."); // Failure message.
var app = builder.Build();
IValidateOptions<TOptions>
および IValidatableObject
次のクラスは、IValidateOptions<TOptions> を実装します。
public class MyConfigValidation : IValidateOptions<MyConfigOptions>
{
public MyConfigOptions _config { get; private set; }
public MyConfigValidation(IConfiguration config)
{
_config = config.GetSection(MyConfigOptions.MyConfig)
.Get<MyConfigOptions>();
}
public ValidateOptionsResult Validate(string name, MyConfigOptions options)
{
string? vor = null;
var rx = new Regex(@"^[a-zA-Z''-'\s]{1,40}$");
var match = rx.Match(options.Key1!);
if (string.IsNullOrEmpty(match.Value))
{
vor = $"{options.Key1} doesn't match RegEx \n";
}
if ( options.Key2 < 0 || options.Key2 > 1000)
{
vor = $"{options.Key2} doesn't match Range 0 - 1000 \n";
}
if (_config.Key2 != default)
{
if(_config.Key3 <= _config.Key2)
{
vor += "Key3 must be > than Key2.";
}
}
if (vor != null)
{
return ValidateOptionsResult.Fail(vor);
}
return ValidateOptionsResult.Success;
}
}
IValidateOptions
は、Program.cs
からクラスに検証コードを移動できるようにします。
前のコードを使用すると、次のコードにより Program.cs
で検証が有効になります。
using Microsoft.Extensions.Options;
using OptionsValidationSample.Configuration;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.Configure<MyConfigOptions>(builder.Configuration.GetSection(
MyConfigOptions.MyConfig));
builder.Services.AddSingleton<IValidateOptions
<MyConfigOptions>, MyConfigValidation>();
var app = builder.Build();
オプションの検証では IValidatableObject もサポートされています。 クラス自体に含まれるクラスのクラス レベルの検証を実行するには:
IValidatableObject
インターフェイスとその Validate メソッドをクラス内で実装します。Program.cs
内で ValidateDataAnnotations を呼び出します。
ValidateOnStart
オプションの検証は、IOptions<TOptions>、IOptionsSnapshot<TOptions>、または IOptionsMonitor<TOptions> の実装が作成されるときに初めて実行されます。 オプションの検証を能動的に実行するには、アプリの開始時に Program.cs
で ValidateOnStart を呼び出します。
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig))
.ValidateDataAnnotations()
.ValidateOnStart();
オプションのポスト構成
ポスト構成を IPostConfigureOptions<TOptions> を使用して設定します。 ポスト構成は、すべての IConfigureOptions<TOptions> 構成が行われた後で実行されます。
using OptionsValidationSample.Configuration;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig));
builder.Services.PostConfigure<MyConfigOptions>(myOptions =>
{
myOptions.Key1 = "post_configured_key1_value";
});
PostConfigure は、名前付きオプションのポスト構成に使用できます。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.Configure<TopItemSettings>(TopItemSettings.Month,
builder.Configuration.GetSection("TopItem:Month"));
builder.Services.Configure<TopItemSettings>(TopItemSettings.Year,
builder.Configuration.GetSection("TopItem:Year"));
builder.Services.PostConfigure<TopItemSettings>("Month", myOptions =>
{
myOptions.Name = "post_configured_name_value";
myOptions.Model = "post_configured_model_value";
});
var app = builder.Build();
すべての構成インスタンスをポスト構成するには、PostConfigureAll を使用します。
using OptionsValidationSample.Configuration;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddOptions<MyConfigOptions>()
.Bind(builder.Configuration.GetSection(MyConfigOptions.MyConfig));
builder.Services.PostConfigureAll<MyConfigOptions>(myOptions =>
{
myOptions.Key1 = "post_configured_key1_value";
});
Program.cs
のアクセスオプション
Program.cs
で IOptions<TOptions> または IOptionsMonitor<TOptions> にアクセスするには、WebApplication.Services で GetRequiredService を呼び出します。
var app = builder.Build();
var option1 = app.Services.GetRequiredService<IOptionsMonitor<MyOptions>>()
.CurrentValue.Option1;
その他のリソース
- サンプル コードを表示またはダウンロードします (ダウンロード方法)。
作成者: Kirk Larkin および Rick Anderson
オプション パターンは、クラスを使用して、関連する設定のグループに、厳密に型指定されたアクセスを提供します。 構成設定がシナリオ別に個々のクラスに分離されるとき、アプリは次の 2 つの重要なソフトウェア エンジニアリング原則に従います。
構成データを検証するメカニズムもオプションによって提供されます。 詳しくは、「オプションの検証」セクションをご覧ください。
このトピックでは、ASP.NET Core でのオプションのパターンについて説明します。 コンソール アプリでオプションのパターンを使用する方法の詳細については、「.NET でのオプションのパターン」を参照してください。
サンプル コードを表示またはダウンロードします (ダウンロード方法)。
階層的な構成をバインドする
関連する構成値を読み取る方法としては、オプション パターンを使用することをお勧めします。 たとえば、以下の構成値を読み取るには、次のようにします:
"Position": {
"Title": "Editor",
"Name": "Joe Smith"
}
次の PositionOptions
クラスを作成します:
public class PositionOptions
{
public const string Position = "Position";
public string Title { get; set; }
public string Name { get; set; }
}
オプション クラス:
- パラメーターのないパブリック コンストラクターを持った非抽象でなければなりません。
- 型のパブリックな読み取り/書き込みプロパティは、すべてバインドされます。
- フィールドはバインド "されません"。 上のコード
Position
はバインドされません。Position
プロパティは、クラスを構成プロバイダーにバインドするときに、アプリで文字列"Position"
をハードコーディングする必要をなくすために使用されます。
コード例を次に示します。
- ConfigurationBinder.Bind を呼び出して、
PositionOptions
クラスをPosition
セクションにバインドします。 Position
構成データを表示します。
public class Test22Model : PageModel
{
private readonly IConfiguration Configuration;
public Test22Model(IConfiguration configuration)
{
Configuration = configuration;
}
public ContentResult OnGet()
{
var positionOptions = new PositionOptions();
Configuration.GetSection(PositionOptions.Position).Bind(positionOptions);
return Content($"Title: {positionOptions.Title} \n" +
$"Name: {positionOptions.Name}");
}
}
上のコードでは、アプリが開始された後の JSON 構成ファイルへの変更が既定で読み取られます。
ConfigurationBinder.Get<T>
指定された型をバインドして返します。 ConfigurationBinder.Get<T>
は ConfigurationBinder.Bind
を使用するよりも便利な場合があります。 次のコードは、PositionOptions
クラスで ConfigurationBinder.Get<T>
を使用する方法を示しています:
public class Test21Model : PageModel
{
private readonly IConfiguration Configuration;
public PositionOptions positionOptions { get; private set; }
public Test21Model(IConfiguration configuration)
{
Configuration = configuration;
}
public ContentResult OnGet()
{
positionOptions = Configuration.GetSection(PositionOptions.Position)
.Get<PositionOptions>();
return Content($"Title: {positionOptions.Title} \n" +
$"Name: {positionOptions.Name}");
}
}
上のコードでは、アプリが開始された後の JSON 構成ファイルへの変更が既定で読み取られます。
"オプション パターン" を使用するときのもう 1 つの方法は、Position
セクションをバインドし、それを依存関係挿入サービス コンテナーに追加することです。 次のコードでは、PositionOptions
は Configure でサービスコンテナーに追加され、構成にバインドされます:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<PositionOptions>(Configuration.GetSection(
PositionOptions.Position));
services.AddRazorPages();
}
下記のコードは、上記のコードを使用して位置オプションを読み取ります:
public class Test2Model : PageModel
{
private readonly PositionOptions _options;
public Test2Model(IOptions<PositionOptions> options)
{
_options = options.Value;
}
public ContentResult OnGet()
{
return Content($"Title: {_options.Title} \n" +
$"Name: {_options.Name}");
}
}
上のコードでは、アプリが開始された後の JSON 構成ファイルへの変更は読み取られ "ません"。 アプリの開始後に変更を読み取るには、IOptionsSnapshot を使用します。
オプションのインターフェイス
- 要求ごとにオプションを再計算する必要があるシナリオで役立ちます。 詳細については、「IOptionsSnapshot を使用して更新データを読み取る」を参照してください。
- スコープとして登録されているため、シングルトン サービスには挿入できません。
- 名前付きオプションをサポートします。
- オプションを取得し、
TOptions
インスタンスのオプション通知を管理するために使用されます。 - シングルトンとして登録されており、任意のサービスの有効期間に挿入できます。
- サポートするものは次のとおりです。
- 変更通知
- 名前付きオプション
- 再読み込み可能な構成
- 選択的なオプションの無効化 (IOptionsMonitorCache<TOptions>)
ポスト構成のシナリオでは、すべての IConfigureOptions<TOptions> 構成が行われた後で、オプションを設定または変更できます。
IOptionsFactory<TOptions> は、新しいオプション インスタンスを作成します。 Create メソッドが 1 つ含まれています。 既定の実装では、登録されている IConfigureOptions<TOptions> と IPostConfigureOptions<TOptions> がすべて受け取られ、先にすべての構成を実行し、その後、ポスト構成を実行します。 IConfigureNamedOptions<TOptions> と IConfigureOptions<TOptions> が区別され、適切なインターフェイスのみが呼び出されます。
IOptionsMonitorCache<TOptions> は IOptionsMonitor<TOptions> によって使用され、TOptions
インスタンスをキャッシュします。 IOptionsMonitorCache<TOptions> は、値が再計算されるよう、モニターのオプション インスタンスを無効にします (TryRemove)。 TryAdd を利用し、手動で値を入力できます。 Clear メソッドは、すべての名前付きインスタンスをオンデマンドで再作成するときに使用されます。
IOptionsSnapshot を使用して更新データを読み取る
IOptionsSnapshot<TOptions> を使用すると、オプションは要求の有効期間中にアクセスされ、キャッシュされたとき、要求につき 1 回計算されます。 更新された構成値の読み取りをサポートする構成プロバイダーを使用しているとき、構成の変更は、アプリの開始後に読み取られます。
IOptionsMonitor
と IOptionsSnapshot
の違いは次のとおりです。
IOptionsMonitor
は常に最新のオプション値を取得するシングルトン サービスです。これは、シングルトンの依存関係で特に便利です。IOptionsSnapshot
はスコープ サービスであり、IOptionsSnapshot<T>
オブジェクトの構築時にオプションのスナップショットを提供します。 オプションのスナップショットは、一時的な依存関係およびスコープのある依存関係で使用されるように設計されています。
次のコードでは IOptionsSnapshot<TOptions> を使用します。
public class TestSnapModel : PageModel
{
private readonly MyOptions _snapshotOptions;
public TestSnapModel(IOptionsSnapshot<MyOptions> snapshotOptionsAccessor)
{
_snapshotOptions = snapshotOptionsAccessor.Value;
}
public ContentResult OnGet()
{
return Content($"Option1: {_snapshotOptions.Option1} \n" +
$"Option2: {_snapshotOptions.Option2}");
}
}
次のコードは、MyOptions
のバインド先となる構成インスタンスを登録します。
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));
services.AddRazorPages();
}
上記のコードでは、アプリが開始された後の JSON 構成ファイルへの変更が読み取られます。
IOptionsMonitor
次のコードは、MyOptions
のバインド先となる構成インスタンスを登録します。
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));
services.AddRazorPages();
}
IOptionsMonitor<TOptions> の使用例を次に示します。
public class TestMonitorModel : PageModel
{
private readonly IOptionsMonitor<MyOptions> _optionsDelegate;
public TestMonitorModel(IOptionsMonitor<MyOptions> optionsDelegate )
{
_optionsDelegate = optionsDelegate;
}
public ContentResult OnGet()
{
return Content($"Option1: {_optionsDelegate.CurrentValue.Option1} \n" +
$"Option2: {_optionsDelegate.CurrentValue.Option2}");
}
}
上のコードでは、アプリが開始された後の JSON 構成ファイルへの変更が既定で読み取られます。
IConfigureNamedOptions を使用した名前付きオプションのサポート
名前付きオプション:
- 複数の構成セクションが同じプロパティにバインドされている場合に便利です。
- 大文字と小文字の区別があります。
次の appsettings.json
ファイルを考えてみます。
{
"TopItem": {
"Month": {
"Name": "Green Widget",
"Model": "GW46"
},
"Year": {
"Name": "Orange Gadget",
"Model": "OG35"
}
}
}
TopItem:Month
と TopItem:Year
をバインドする 2 つのクラスを作成するのではなく、各セクションに対して次のクラスを使用します。
public class TopItemSettings
{
public const string Month = "Month";
public const string Year = "Year";
public string Name { get; set; }
public string Model { get; set; }
}
次のコードは、名前付きオプションを構成します。
public void ConfigureServices(IServiceCollection services)
{
services.Configure<TopItemSettings>(TopItemSettings.Month,
Configuration.GetSection("TopItem:Month"));
services.Configure<TopItemSettings>(TopItemSettings.Year,
Configuration.GetSection("TopItem:Year"));
services.AddRazorPages();
}
次のコードは、名前付きオプションを表示します。
public class TestNOModel : PageModel
{
private readonly TopItemSettings _monthTopItem;
private readonly TopItemSettings _yearTopItem;
public TestNOModel(IOptionsSnapshot<TopItemSettings> namedOptionsAccessor)
{
_monthTopItem = namedOptionsAccessor.Get(TopItemSettings.Month);
_yearTopItem = namedOptionsAccessor.Get(TopItemSettings.Year);
}
public ContentResult OnGet()
{
return Content($"Month:Name {_monthTopItem.Name} \n" +
$"Month:Model {_monthTopItem.Model} \n\n" +
$"Year:Name {_yearTopItem.Name} \n" +
$"Year:Model {_yearTopItem.Model} \n" );
}
}
すべてのオプションが名前付きインスタンスです。 IConfigureOptions<TOptions> インスタンスは、string.Empty
である、Options.DefaultName
インスタンスを対象とするものとして処理されます。 IConfigureNamedOptions<TOptions> はまた、IConfigureOptions<TOptions> を実装します。 IOptionsFactory<TOptions> の既定の実装には、それぞれを適切に使用するロジックがあります。 名前付きオプション null
は、特定の名前付きインスタンスではなく、すべての名前付きインスタンスを対象とするために使用されます。 ConfigureAll と PostConfigureAll では、この規則が使用されます。
OptionsBuilder API
OptionsBuilder<TOptions> は、TOptions
インスタンスの構成に使用されます。 OptionsBuilder
は名前付きオプションの作成を簡略化します。これは最初の AddOptions<TOptions>(string optionsName)
の呼び出しに対する 1 つのパラメーターにすぎず、後続のすべての呼び出しが表示されなくなるためです。 サービスの依存関係を受け入れるオプションの検証と ConfigureOptions
のオーバーロードは、OptionsBuilder
を介してのみ可能です。
OptionsBuilder
は、「オプションの検証」セクションで使用されます。
カスタム リポジトリの追加の詳細については、「AddOptions を使用してカスタム リポジトリを構成する」を参照してください。
DI サービスを使用してオプションを構成する
オプションの構成中、次の 2 つの方法で依存関係挿入からサービスにアクセスできます。
OptionsBuilder<TOptions> の Configure に構成デリゲートを渡します。
OptionsBuilder<TOptions>
から Configure のオーバーロードが与えられます。これにより、最大 5 つのサービスを使用してオプションを構成できます。services.AddOptions<MyOptions>("optionalName") .Configure<Service1, Service2, Service3, Service4, Service5>( (o, s, s2, s3, s4, s5) => o.Property = DoSomethingWith(s, s2, s3, s4, s5));
IConfigureOptions<TOptions> または IConfigureNamedOptions<TOptions> を実装する型を作成し、その型をサービスとして登録します。
サービスの作成は複雑なため、Configure に構成デリゲートを渡す方法をおすすめします。 型を作成することは、Configure を呼び出すときにフレームワークが行うことと同じです。 Configure を呼び出すと、一時的な汎用の IConfigureNamedOptions<TOptions> が登録されます。これには、指定された汎用サービスの型を受け入れるコンストラクターが含まれています。
オプションの検証
オプションの検証により、オプションの値を検証できます。
次の appsettings.json
ファイルを考えてみます。
{
"MyConfig": {
"Key1": "My Key One",
"Key2": 10,
"Key3": 32
}
}
次のクラスは、"MyConfig"
構成セクションにバインドされ、いくつかの DataAnnotations
規則を適用します。
public class MyConfigOptions
{
public const string MyConfig = "MyConfig";
[RegularExpression(@"^[a-zA-Z''-'\s]{1,40}$")]
public string Key1 { get; set; }
[Range(0, 1000,
ErrorMessage = "Value for {0} must be between {1} and {2}.")]
public int Key2 { get; set; }
public int Key3 { get; set; }
}
コード例を次に示します。
- AddOptions を呼び出して、
MyConfigOptions
クラスにバインドする OptionsBuilder<TOptions> を取得します。 - ValidateDataAnnotations を呼び出し、
DataAnnotations
を利用した検証を有効にします。
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions<MyConfigOptions>()
.Bind(Configuration.GetSection(MyConfigOptions.MyConfig))
.ValidateDataAnnotations();
services.AddControllersWithViews();
}
ValidateDataAnnotations
拡張メソッドは Microsoft.Extensions.Options.DataAnnotations NuGet パッケージに定義されています。 Microsoft.NET.Sdk.Web
SDK を使用する Web アプリの場合、このパッケージは共有フレームワークから暗黙的に参照されます。
次のコードは、構成値または検証エラーを表示します。
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly IOptions<MyConfigOptions> _config;
public HomeController(IOptions<MyConfigOptions> config,
ILogger<HomeController> logger)
{
_config = config;
_logger = logger;
try
{
var configValue = _config.Value;
}
catch (OptionsValidationException ex)
{
foreach (var failure in ex.Failures)
{
_logger.LogError(failure);
}
}
}
public ContentResult Index()
{
string msg;
try
{
msg = $"Key1: {_config.Value.Key1} \n" +
$"Key2: {_config.Value.Key2} \n" +
$"Key3: {_config.Value.Key3}";
}
catch (OptionsValidationException optValEx)
{
return Content(optValEx.Message);
}
return Content(msg);
}
次のコードは、デリゲートを使用して、より複雑な検証規則を適用します。
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions<MyConfigOptions>()
.Bind(Configuration.GetSection(MyConfigOptions.MyConfig))
.ValidateDataAnnotations()
.Validate(config =>
{
if (config.Key2 != 0)
{
return config.Key3 > config.Key2;
}
return true;
}, "Key3 must be > than Key2."); // Failure message.
services.AddControllersWithViews();
}
複雑な検証用の IValidateOptions
次のクラスは、IValidateOptions<TOptions> を実装します。
public class MyConfigValidation : IValidateOptions<MyConfigOptions>
{
public MyConfigOptions _config { get; private set; }
public MyConfigValidation(IConfiguration config)
{
_config = config.GetSection(MyConfigOptions.MyConfig)
.Get<MyConfigOptions>();
}
public ValidateOptionsResult Validate(string name, MyConfigOptions options)
{
string vor=null;
var rx = new Regex(@"^[a-zA-Z''-'\s]{1,40}$");
var match = rx.Match(options.Key1);
if (string.IsNullOrEmpty(match.Value))
{
vor = $"{options.Key1} doesn't match RegEx \n";
}
if ( options.Key2 < 0 || options.Key2 > 1000)
{
vor = $"{options.Key2} doesn't match Range 0 - 1000 \n";
}
if (_config.Key2 != default)
{
if(_config.Key3 <= _config.Key2)
{
vor += "Key3 must be > than Key2.";
}
}
if (vor != null)
{
return ValidateOptionsResult.Fail(vor);
}
return ValidateOptionsResult.Success;
}
}
IValidateOptions
は、StartUp
からクラスに検証コードを移動できるようにします。
前のコードを使用すると、次のコードにより Startup.ConfigureServices
で検証が有効になります。
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MyConfigOptions>(Configuration.GetSection(
MyConfigOptions.MyConfig));
services.TryAddEnumerable(ServiceDescriptor.Singleton<IValidateOptions
<MyConfigOptions>, MyConfigValidation>());
services.AddControllersWithViews();
}
オプションのポスト構成
ポスト構成を IPostConfigureOptions<TOptions> を使用して設定します。 ポスト構成は、すべての IConfigureOptions<TOptions> 構成が行われた後で実行されます。
services.PostConfigure<MyOptions>(myOptions =>
{
myOptions.Option1 = "post_configured_option1_value";
});
PostConfigure は、名前付きオプションのポスト構成に使用できます。
services.PostConfigure<MyOptions>("named_options_1", myOptions =>
{
myOptions.Option1 = "post_configured_option1_value";
});
すべての構成インスタンスをポスト構成するには、PostConfigureAll を使用します。
services.PostConfigureAll<MyOptions>(myOptions =>
{
myOptions.Option1 = "post_configured_option1_value";
});
スタートアップ時にオプションにアクセスする
サービスは Configure
メソッドの実行前に構築されるため、IOptions<TOptions> および IOptionsMonitor<TOptions> は Startup.Configure
で使用できます。
public void Configure(IApplicationBuilder app,
IOptionsMonitor<MyOptions> optionsAccessor)
{
var option1 = optionsAccessor.CurrentValue.Option1;
}
Startup.ConfigureServices
では IOptions<TOptions> または IOptionsMonitor<TOptions> は使用しないでください。 サービスの登録順序が原因で、オプションの状態が一貫しない場合があります。
Options.ConfigurationExtensions NuGet パッケージ
ASP.NET Core アプリでは、Microsoft.Extensions.Options.ConfigurationExtensions パッケージが暗黙的に参照されます。
ASP.NET Core