選項模式使用類來表示相關設置的組。 當配置設置由方案隔離到單獨的類時,應用遵循兩個重要軟件工程原則:
選項還提供驗證配置數(shù)據(jù)的機制。 有關詳細信息,請參閱選項驗證部分。
引用 Microsoft.AspNetCore.App 元包或?qū)锰砑拥?nbsp;Microsoft.Extensions.Options.ConfigurationExtensions 包。
IOptionsMonitor<TOptions> 用于檢索選項并管理 TOptions 實例的選項通知。IOptionsMonitor<TOptions> 支持以下方案:
后期配置方案允許你在進行所有 IConfigureOptions<TOptions> 配置后設置或更改選項。
IOptionsFactory<TOptions> 負責新建選項實例。 它具有單個 Create 方法。 默認實現(xiàn)采用所有已注冊 IConfigureOptions<TOptions> 和 IPostConfigureOptions<TOptions> 并首先運行所有配置,然后才進行后期配置。 它區(qū)分 IConfigureNamedOptions<TOptions> 和 IConfigureOptions<TOptions>且僅調(diào)用適當?shù)慕涌凇?/p>
IOptionsMonitorCache<TOptions> 由 IOptionsMonitor<TOptions> 用于緩存 TOptions 實例。IOptionsMonitorCache<TOptions> 可使監(jiān)視器中的選項實例無效,以便重新計算值 (TryRemove)??梢酝ㄟ^ TryAdd 手動引入值。 在應按需重新創(chuàng)建所有命名實例時使用 Clear 方法。
IOptionsSnapshot<TOptions> 在每次請求時應重新計算選項的方案中有用。 有關詳細信息,請參閱通過 IOptionsSnapshot 重新加載配置數(shù)據(jù)部分。
IOptions<TOptions> 可用于支持選項。 但是,IOptions<TOptions> 不支持前面的 IOptionsMonitor<TOptions> 方案。 你可以在已使用 IOptions<TOptions> 接口的現(xiàn)有框架和庫中繼續(xù)使用 IOptions<TOptions>,并且不需要 IOptionsMonitor<TOptions> 提供的方案。
常規(guī)選項配置已作為示例 #1 在示例應用中進行了演示。
選項類必須為包含公共無參數(shù)構造函數(shù)的非抽象類。 以下類 MyOptions 具有兩種屬性:Option1 和 Option2。 設置默認值為可選,但以下示例中的類構造函數(shù)設置了 Option1 的默認值。 Option2具有通過直接初始化屬性設置的默認值 (Models/MyOptions.cs):
C#
public class MyOptions
{
public MyOptions()
{
// Set default value.
Option1 = "value1_from_ctor";
}
public string Option1 { get; set; }
public int Option2 { get; set; } = 5;
}
MyOptions 類已通過 Configure 添加到服務容器并綁定到配置:
C#
// Example #1: General configuration
// Register the Configuration instance which MyOptions binds against.
services.Configure<MyOptions>(Configuration);
以下頁面模型通過 IOptionsMonitor<TOptions> 使用構造函數(shù)依賴關系注入來訪問設置 (Pages/Index.cshtml.cs):
C#
private readonly MyOptions _options;
C#
public IndexModel(
IOptionsMonitor<MyOptions> optionsAccessor,
IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig,
IOptionsMonitor<MySubOptions> subOptionsAccessor,
IOptionsSnapshot<MyOptions> snapshotOptionsAccessor,
IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
_options = optionsAccessor.CurrentValue;
_optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
_subOptions = subOptionsAccessor.CurrentValue;
_snapshotOptions = snapshotOptionsAccessor.Value;
_named_options_1 = namedOptionsAccessor.Get("named_options_1");
_named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
C#
// Example #1: Simple options
var option1 = _options.Option1;
var option2 = _options.Option2;
SimpleOptions = $"option1 = {option1}, option2 = {option2}";
示例的 appsettings.json 文件指定 option1 和 option2 的值:
JSON
{
"option1": "value1_from_json",
"option2": -1,
"subsection": {
"suboption1": "subvalue1_from_json",
"suboption2": 200
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
運行應用時,頁面模型的 OnGet 方法返回顯示選項類值的字符串:
HTML
option1 = value1_from_json, option2 = -1
備注
使用自定義 ConfigurationBuilder 從設置文件加載選項配置時,請確認基路徑設置正確:
C#
var configBuilder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true);
var config = configBuilder.Build();
services.Configure<MyOptions>(config);
通過 CreateDefaultBuilder 從設置文件加載選項配置時,不需要顯式設置基路徑。
通過委托配置簡單選項已作為示例 #2 在示例應用中進行了演示。
使用委托設置選項值。 此示例應用使用 MyOptionsWithDelegateConfig 類 (Models/MyOptionsWithDelegateConfig.cs):
C#
public class MyOptionsWithDelegateConfig
{
public MyOptionsWithDelegateConfig()
{
// Set default value.
Option1 = "value1_from_ctor";
}
public string Option1 { get; set; }
public int Option2 { get; set; } = 5;
}
在以下代碼中,已向服務容器添加第二個 IConfigureOptions<TOptions> 服務。 它通過 MyOptionsWithDelegateConfig 使用委托來配置綁定:
C#
// Example #2: Options bound and configured by a delegate
services.Configure<MyOptionsWithDelegateConfig>(myOptions =>
{
myOptions.Option1 = "value1_configured_by_delegate";
myOptions.Option2 = 500;
});
Index.cshtml.cs:
C#
private readonly MyOptionsWithDelegateConfig _optionsWithDelegateConfig;
C#
public IndexModel(
IOptionsMonitor<MyOptions> optionsAccessor,
IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig,
IOptionsMonitor<MySubOptions> subOptionsAccessor,
IOptionsSnapshot<MyOptions> snapshotOptionsAccessor,
IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
_options = optionsAccessor.CurrentValue;
_optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
_subOptions = subOptionsAccessor.CurrentValue;
_snapshotOptions = snapshotOptionsAccessor.Value;
_named_options_1 = namedOptionsAccessor.Get("named_options_1");
_named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
C#
// Example #2: Options configured by delegate
var delegate_config_option1 = _optionsWithDelegateConfig.Option1;
var delegate_config_option2 = _optionsWithDelegateConfig.Option2;
SimpleOptionsWithDelegateConfig =
$"delegate_option1 = {delegate_config_option1}, " +
$"delegate_option2 = {delegate_config_option2}";
可添加多個配置提供程序。 配置提供程序可從 NuGet 包中獲取,并按照注冊的順序應用。 有關更多信息,請參見ASP.NET Core 中的配置。
每次調(diào)用 Configure 都會將 IConfigureOptions<TOptions> 服務添加到服務容器。 在前面的示例中,Option1 和 Option2 的值同時在 appsettings.json 中指定,但 Option1 和 Option2 的值被配置的委托替代。
當啟用多個配置服務時,指定的最后一個配置源優(yōu)于其他源,由其設置配置值。 運行應用時,頁面模型的 OnGet 方法返回顯示選項類值的字符串:
HTML
delegate_option1 = value1_configured_by_delegate, delegate_option2 = 500
子選項配置已作為示例 #3 在示例應用中進行了演示。
應用應創(chuàng)建適用于應用中特定方案組(類)的選項類。 需要配置值的部分應用應僅有權訪問其使用的配置值。
將選項綁定到配置時,選項類型中的每個屬性都將綁定到窗體 property[:sub-property:] 的配置鍵。例如,MyOptions.Option1 屬性將綁定到從 appsettings.json 中的 option1 屬性讀取的鍵 Option1。
在以下代碼中,已向服務容器添加第三個 IConfigureOptions<TOptions> 服務。 它將 MySubOptions綁定到 appsettings.json 文件的 subsection 部分:
C#
// Example #3: Suboptions
// Bind options using a sub-section of the appsettings.json file.
services.Configure<MySubOptions>(Configuration.GetSection("subsection"));
GetSection 擴展方法需要 Microsoft.Extensions.Options.ConfigurationExtensions NuGet 包。 如果應用使用 Microsoft.AspNetCore.App metapackage 元包(ASP.NET Core 2.1 或更高版本),將自動包含此包。
示例的 appsettings.json 文件定義具有 suboption1 和 suboption2 的鍵的 subsection 成員:
JSON
{
"option1": "value1_from_json",
"option2": -1,
"subsection": {
"suboption1": "subvalue1_from_json",
"suboption2": 200
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
MySubOptions 類將屬性 SubOption1 和 SubOption2 定義為保留選項值 (Models/MySubOptions.cs):
C#
public class MySubOptions
{
public MySubOptions()
{
// Set default values.
SubOption1 = "value1_from_ctor";
SubOption2 = 5;
}
public string SubOption1 { get; set; }
public int SubOption2 { get; set; }
}
頁面模型的 OnGet 方法返回包含選項值的字符串 (Pages/Index.cshtml.cs):
C#
private readonly MySubOptions _subOptions;
C#
public IndexModel(
IOptionsMonitor<MyOptions> optionsAccessor,
IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig,
IOptionsMonitor<MySubOptions> subOptionsAccessor,
IOptionsSnapshot<MyOptions> snapshotOptionsAccessor,
IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
_options = optionsAccessor.CurrentValue;
_optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
_subOptions = subOptionsAccessor.CurrentValue;
_snapshotOptions = snapshotOptionsAccessor.Value;
_named_options_1 = namedOptionsAccessor.Get("named_options_1");
_named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
C#
// Example #3: Suboptions
var subOption1 = _subOptions.SubOption1;
var subOption2 = _subOptions.SubOption2;
SubOptions = $"subOption1 = {subOption1}, subOption2 = {subOption2}";
運行應用時,OnGet 方法返回顯示子選項類值的字符串:
HTML
subOption1 = subvalue1_from_json, subOption2 = 200
視圖模型或通過直接視圖注入提供的選項已作為示例 #4 在示例應用中進行了演示。
可在視圖模型中或通過將 IOptionsMonitor<TOptions> 直接注入到視圖 (Pages/Index.cshtml.cs) 來提供選項:
C#
private readonly MyOptions _options;
C#
public IndexModel(
IOptionsMonitor<MyOptions> optionsAccessor,
IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig,
IOptionsMonitor<MySubOptions> subOptionsAccessor,
IOptionsSnapshot<MyOptions> snapshotOptionsAccessor,
IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
_options = optionsAccessor.CurrentValue;
_optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
_subOptions = subOptionsAccessor.CurrentValue;
_snapshotOptions = snapshotOptionsAccessor.Value;
_named_options_1 = namedOptionsAccessor.Get("named_options_1");
_named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
C#
// Example #4: Bind options directly to the page
MyOptions = _options;
示例應用演示如何使用 @inject 指令注入 IOptionsMonitor<MyOptions>:
CSHTML
@page
@model IndexModel
@using Microsoft.Extensions.Options
@inject IOptionsMonitor<MyOptions> OptionsAccessor
@{
ViewData["Title"] = "Options Sample";
}
<h1>@ViewData["Title"]</h1>
當應用運行時,選項值顯示在呈現(xiàn)的頁面中:
通過 IOptionsSnapshot<TOptions> 重新加載配置數(shù)據(jù)已作為示例 #5 在示例應用中進行了演示。
IOptionsSnapshot<TOptions> 支持包含最小處理開銷的重新加載選項。
針對請求生存期訪問和緩存選項時,每個請求只能計算一次選項。
以下示例演示如何在更改 appsettings.json (Pages/Index.cshtml.cs) 后創(chuàng)建新的 IOptionsSnapshot<TOptions>。 在更改文件和重新加載配置之前,針對服務器的多個請求返回 appsettings.json 文件提供的常數(shù)值。
C#
private readonly MyOptions _snapshotOptions;
C#
public IndexModel(
IOptionsMonitor<MyOptions> optionsAccessor,
IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig,
IOptionsMonitor<MySubOptions> subOptionsAccessor,
IOptionsSnapshot<MyOptions> snapshotOptionsAccessor,
IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
_options = optionsAccessor.CurrentValue;
_optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
_subOptions = subOptionsAccessor.CurrentValue;
_snapshotOptions = snapshotOptionsAccessor.Value;
_named_options_1 = namedOptionsAccessor.Get("named_options_1");
_named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
C#
// Example #5: Snapshot options
var snapshotOption1 = _snapshotOptions.Option1;
var snapshotOption2 = _snapshotOptions.Option2;
SnapshotOptions =
$"snapshot option1 = {snapshotOption1}, " +
$"snapshot option2 = {snapshotOption2}";
下圖顯示從 appsettings.json 文件加載的初始 option1 和 option2 值:
HTML
snapshot option1 = value1_from_json, snapshot option2 = -1
將 appsettings.json 文件中的值更改為 value1_from_json UPDATED 和 200。 保存 appsettings.json 文件。 刷新瀏覽器,查看更新的選項值:
HTML
snapshot option1 = value1_from_json UPDATED, snapshot option2 = 200
包含 IConfigureNamedOptions<TOptions> 的命名選項支持已作為示例 #6 在示例應用中進行了演示。
命名選項支持允許應用在命名選項配置之間進行區(qū)分。 在示例應用中,命名選項通過 OptionsServiceCollectionExtensions.Configure 進行聲明,其調(diào)用擴展方法 ConfigureNamedOptions<TOptions>.Configure:
C#
// Example #6: Named options (named_options_1)
// Register the ConfigurationBuilder instance which MyOptions binds against.
// Specify that the options loaded from configuration are named
// "named_options_1".
services.Configure<MyOptions>("named_options_1", Configuration);
// Example #6: Named options (named_options_2)
// Specify that the options loaded from the MyOptions class are named
// "named_options_2".
// Use a delegate to configure option values.
services.Configure<MyOptions>("named_options_2", myOptions =>
{
myOptions.Option1 = "named_options_2_value1_from_action";
});
示例應用通過 Get (Pages/Index.cshtml.cs) 訪問命名選項:
C#
private readonly MyOptions _named_options_1;
private readonly MyOptions _named_options_2;
C#
public IndexModel(
IOptionsMonitor<MyOptions> optionsAccessor,
IOptionsMonitor<MyOptionsWithDelegateConfig> optionsAccessorWithDelegateConfig,
IOptionsMonitor<MySubOptions> subOptionsAccessor,
IOptionsSnapshot<MyOptions> snapshotOptionsAccessor,
IOptionsSnapshot<MyOptions> namedOptionsAccessor)
{
_options = optionsAccessor.CurrentValue;
_optionsWithDelegateConfig = optionsAccessorWithDelegateConfig.CurrentValue;
_subOptions = subOptionsAccessor.CurrentValue;
_snapshotOptions = snapshotOptionsAccessor.Value;
_named_options_1 = namedOptionsAccessor.Get("named_options_1");
_named_options_2 = namedOptionsAccessor.Get("named_options_2");
}
C#
// Example #6: Named options
var named_options_1 =
$"named_options_1: option1 = {_named_options_1.Option1}, " +
$"option2 = {_named_options_1.Option2}";
var named_options_2 =
$"named_options_2: option1 = {_named_options_2.Option1}, " +
$"option2 = {_named_options_2.Option2}";
NamedOptions = $"{named_options_1} {named_options_2}";
運行示例應用,將返回命名選項:
HTML
named_options_1: option1 = value1_from_json, option2 = -1
named_options_2: option1 = named_options_2_value1_from_action, option2 = 5
從配置中提供從 appsettings.json 文件中加載的 named_options_1 值。 通過以下內(nèi)容提供 named_options_2 值:
使用 ConfigureAll 方法配置所有選項實例。 以下代碼將針對包含公共值的所有配置實例配置 Option1。 將以下代碼手動添加到 Startup.ConfigureServices 方法:
C#
services.ConfigureAll<MyOptions>(myOptions =>
{
myOptions.Option1 = "ConfigureAll replacement value";
});
添加代碼后運行示例應用將產(chǎn)生以下結果:
HTML
named_options_1: option1 = ConfigureAll replacement value, option2 = -1
named_options_2: option1 = ConfigureAll replacement value, option2 = 5
備注
所有選項都是命名實例。 現(xiàn)有 IConfigureOptions<TOptions> 實例將被視為面向為 string.Empty 的 Options.DefaultName 實例。 IConfigureNamedOptions<TOptions> 還可實現(xiàn) IConfigureOptions<TOptions>。 IOptionsFactory<TOptions> 的默認實現(xiàn)具有適當?shù)厥褂妹總€實例的邏輯。 null 命名選項用于面向所有命名實例而不是某一特定命名實例(ConfigureAll 和 PostConfigureAll 使用此約定)。
OptionsBuilder<TOptions> 用于配置 TOptions 實例。 OptionsBuilder 簡化了創(chuàng)建命名選項的過程,因為它只是初始 AddOptions<TOptions>(string optionsName) 調(diào)用的單個參數(shù),而不會出現(xiàn)在所有后續(xù)調(diào)用中。 選項驗證和接受服務依賴關系的 ConfigureOptions 重載僅可通過 OptionsBuilder 獲得。
C#
// Options.DefaultName = "" is used.
services.AddOptions<MyOptions>().Configure(o => o.Property = "default");
services.AddOptions<MyOptions>("optionalName")
.Configure(o => o.Property = "named");
在配置選項時,可以通過以下兩種方式通過依賴關系注入訪問其他服務:
建議將配置委托傳遞給 Configure,因為創(chuàng)建服務較復雜。 在使用 Configure時,創(chuàng)建你自己的類型等效于框架為你執(zhí)行的操作。 調(diào)用 Configure 會注冊臨時泛型 IConfigureNamedOptions<TOptions>,它具有接受指定的泛型服務類型的構造函數(shù)。
借助選項驗證,可以驗證已配置的選項。 使用驗證方法調(diào)用 Validate。如果選項有效,方法返回 true;如果無效,方法返回 false:
C#
// Registration
services.AddOptions<MyOptions>("optionalOptionsName")
.Configure(o => { }) // Configure the options
.Validate(o => YourValidationShouldReturnTrueIfValid(o),
"custom error");
// Consumption
var monitor = services.BuildServiceProvider()
.GetService<IOptionsMonitor<MyOptions>>();
try
{
var options = monitor.Get("optionalOptionsName");
}
catch (OptionsValidationException e)
{
// e.OptionsName returns "optionalOptionsName"
// e.OptionsType returns typeof(MyOptions)
// e.Failures returns a list of errors, which would contain
// "custom error"
}
上面的示例將命名選項實例設置為 optionalOptionsName。 默認選項實例為 Options.DefaultName。
選項驗證在選項實例創(chuàng)建后運行。 系統(tǒng)保證在選項實例首次獲得訪問時通過驗證。
重要
選項驗證無法防止在最初配置和驗證選項后發(fā)生選項修改。
Validate 方法接受 Func<TOptions, bool>。 若要完全自定義驗證,請實現(xiàn) IValidateOptions<TOptions>,它支持:
IValidateOptions 驗證:
通過接口實現(xiàn)返回 ValidateOptionsResult:
C#
public interface IValidateOptions<TOptions> where TOptions : class
{
ValidateOptionsResult Validate(string name, TOptions options);
}
通過調(diào)用 OptionsBuilder<TOptions> 上的 ValidateDataAnnotations 方法,可以從 Microsoft.Extensions.Options.DataAnnotations 包中獲得基于數(shù)據(jù)注釋的驗證。Microsoft.AspNetCore.App 元包(ASP.NET Core 2.2 或更高版本)中包括 Microsoft.Extensions.Options.DataAnnotations。
C#
private class AnnotatedOptions
{
[Required]
public string Required { get; set; }
[StringLength(5, ErrorMessage = "Too long.")]
public string StringLength { get; set; }
[Range(-5, 5, ErrorMessage = "Out of range.")]
public int IntRange { get; set; }
}
[Fact]
public void CanValidateDataAnnotations()
{
var services = new ServiceCollection();
services.AddOptions<AnnotatedOptions>()
.Configure(o =>
{
o.StringLength = "111111";
o.IntRange = 10;
o.Custom = "nowhere";
})
.ValidateDataAnnotations();
var sp = services.BuildServiceProvider();
var error = Assert.Throws<OptionsValidationException>(() =>
sp.GetRequiredService<IOptionsMonitor<AnnotatedOptions>>().Value);
ValidateFailure<AnnotatedOptions>(error, Options.DefaultName, 1,
"DataAnnotation validation failed for members Required " +
"with the error 'The Required field is required.'.",
"DataAnnotation validation failed for members StringLength " +
"with the error 'Too long.'.",
"DataAnnotation validation failed for members IntRange " +
"with the error 'Out of range.'.");
}
設計團隊正在考慮為未來版本提供預先驗證(在啟動時快速失?。?。
使用 IPostConfigureOptions<TOptions> 設置后期配置。 進行所有 IConfigureOptions<TOptions> 配置后運行后期配置:
C#
services.PostConfigure<MyOptions>(myOptions =>
{
myOptions.Option1 = "post_configured_option1_value";
});
PostConfigure 可用于對命名選項進行后期配置:
C#
services.PostConfigure<MyOptions>("named_options_1", myOptions =>
{
myOptions.Option1 = "post_configured_option1_value";
});
使用 PostConfigureAll 對所有配置實例進行后期配置:
C#
services.PostConfigureAll<MyOptions>(myOptions =>
{
myOptions.Option1 = "post_configured_option1_value";
});
IOptions<TOptions> 和 IOptionsMonitor<TOptions> 可用于 Startup.Configure 中,因為在 Configure 方法執(zhí)行之前已生成服務。
C#
public void Configure(IApplicationBuilder app, IOptionsMonitor<MyOptions> optionsAccessor)
{
var option1 = optionsAccessor.CurrentValue.Option1;
}
不使用 Startup.ConfigureServices 中的 IOptions<TOptions> 或 IOptionsMonitor<TOptions>。 由于服務注冊的順序,可能存在不一致的選項狀態(tài)。
更多建議: