目錄
前言:
選項模式使用的 Microsoft.Extensions.Options.ConfigurationExtensions 包已在 ASP.NET Core 應用中隱式引用。
全文中我們全部使用一個名稱爲 MyOptions 的類:
public class MyOptions : IDisposable
{
public MyOptions()
{
Option1 = "value_ctor";
}
public string Option1 { get; set; }
[Range(0, int.MaxValue)]
public int Option2 { get; set; } = 5;
public void Dispose()
{
Console.WriteLine($"MyOptions Disposed:{GetHashCode()}");
}
}
配置文件 appsettings.json 的內容:
{
"option1": "value_json",
"option2": -1,
"subsection": {
"option1": "subvalue_json",
"option2": 55555
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
本人在進行測試時,發現 選項模式的實例並不會走 Dispose() 方法 。
一、依賴注入的生命週期
在選項模式中實例的生命週期由注入時使用的接口決定,
有IOptions
、 IOptionsMonitor
和 IOptionsSnapshot
共三個,
其中 IOptions
、 IOptionsMonitor
的生命週期是單例模式,生命週期和 AddSingleton
類似,
IOptions
沒有數據熱更新,讀取的值永遠不會變。
IOptionsMonitor
和 IOptionsSnapshot
有數據熱更新,會保持最新值。
剩下的 IOptionsSnapshot
是作用域服務(快照),生命週期和 AddScoped
類似。
一、常規選項配置
MyOptions 類已通過 Configure 添加到服務容器並綁定到配置:
#region ConfigureServices
services.Configure<MyOptions>(Configuration);
#endregion
#region [ApiController]
private readonly MyOptions _options;
public HomeController(IOptionsMonitor<MyOptions> options)
{
_options = options.CurrentValue;
}
[HttpGet]
public MyOptions Index() { return _options; }
#endregion
訪問 /Home/Index 得到的值爲:
{"option1":"value_json","option2":-1}
可以看到,配置文件中的值已將實例的默認值覆蓋了。
備註
使用自定義 ConfigurationBuilder 從設置文件加載選項配置:
#region ConfigureServices var configBuilder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json", optional: true); var config = configBuilder.Build(); services.Configure<MyOptions>(config); #endregion
二、通過委託配置簡單選項
使用委託設置選項值:
#region ConfigureServices
services.Configure<MyOptions>(myOptions =>
{
myOptions.Option1 = "value_delegate";
myOptions.Option2 = 2333;
});
#endregion
訪問 /Home/Index 得到的值爲:
{"option1":"value_delegate","option2":2333}
可以看到,MyOptions 使用委託配置綁定覆蓋了默認值,
當啓用多個配置服務時,指定的最後一個配置源優於其他源 。
三、子選項配置
需要配置值的部分應用時可以使用子選項配置
#region ConfigureServices
services.Configure<MyOptions>(Configuration.GetSection("subsection"));
#endregion
訪問 /Home/Index 得到的值爲:
{"option1":"subvalue_json","option2":55555}
四、重新加載配置數據
使用選項框架時,配置文件默認是熱更新的。
我們運行 子選項配置 中的代碼
訪問 /Home/Index 得到的值爲:
{"option1":"subvalue_json","option2":55555}
程序運行時,我們將 appsettings.json
文件中的 subsection
節點改爲如下:
"subsection": {
"option1": "subvalue_json new",
"option2": 12345
},
再次訪問 /Home/Index 得到的值爲:
{"option1":"subvalue_json new","option2":12345}
五、命名選項
命名選項支持允許應用在命名選項配置之間進行區分
#region ConfigureServices
services.Configure<MyOptions>("options_1", Configuration);
services.Configure<MyOptions>("options_2", myOptions =>
{
myOptions.Option1 = "options_2_value1_action";
});
#endregion
#region [ApiController]
private readonly MyOptions _options;
private readonly MyOptions _options_1;
private readonly MyOptions _options_2;
public HomeController(IOptionsMonitor<MyOptions> options)
{
_options = options.CurrentValue;
_options_1 = options.Get("options_1");
_options_2 = options.Get("options_2");
}
[HttpGet]
public List<MyOptions> Index()
{
var result = new List<MyOptions>() {
_options,_options_1,_options_2
};
return result;
}
#endregion
訪問 /Home/Index 得到的值爲:
[
{"option1":"value_ctor","option2":5},
{"option1":"value_json","option2":-1},
{"option1":"options_2_value1_action","option2":5}
]
可以看到,各個MyOptions 實例的不同,
無命名的取的是默認值,options_1 取的是配置文件的值,options_2 取的是委託配置。
六、配置所有選項
可以使用 ConfigureAll 方法配置所有選項實例。
#region ConfigureServices
services.Configure<MyOptions>("options_1", Configuration);
services.Configure<MyOptions>("options_2", myOptions =>
{
myOptions.Option1 = "options_2_value1_action";
});
services.ConfigureAll<MyOptions>(myOptions =>
{
myOptions.Option1 = "ConfigureAll value";
});
#endregion
訪問 /Home/Index 得到的值爲:
[
{"option1":"ConfigureAll value","option2":5},
{"option1":"ConfigureAll value","option2":-1},
{"option1":"ConfigureAll value","option2":5}
]
可以看到每個MyOptions實例的 option1 都被覆蓋了
七、OptionsBuilder API
OptionsBuilder 簡化了創建命名選項的過程,
#region ConfigureServices
services.AddOptions<MyOptions>().Configure(o => o.Option1 = "options_builder");
services.AddOptions<MyOptions>("options_1").Configure(o =>
{
Configuration.Bind(o);
// o.Option1 = "default value";
// o.Option2 = 2000;
});
#endregion
訪問 /Home/Index 得到的值爲:
[
{"option1":"options_builder","option2":5},
{"option1":"default value","option2":2000},
{"option1":"value_ctor","option2":5}
]
八、使用 DI 服務配置選項
可以使用DI服務進行配置
#region ConfigEntity
public class AppConfig { public string Option1 { get; set; } = "SAP"; }
public class PrjConfig { public int Option2 { get; set; } = 69; }
#endregion
#region ConfigureServices
services.AddScoped<AppConfig>();
services.AddScoped<PrjConfig>();
services.AddOptions<MyOptions>("optionalName")
.Configure<AppConfig, PrjConfig>((o, s, b) =>
{
o.Option1 = s.Option1;
o.Option2 = b.Option2;
});
#endregion
#region [ApiController]
private readonly MyOptions _options;
public HomeController(IOptionsSnapshot<MyOptions> options)
{
_options = options.Get("optionalName");
}
[HttpGet]
public MyOptions Index() { return _options; }
#endregion
訪問 /Home/Index 得到的值爲:
{"option1":"SAP","option2":69}
九、後期配置
使用 IPostConfigureOptions<TOptions>
設置後期配置
#region ConfigureServices
// PostConfigure 可用於後期配置
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";
});
#endregion
十、選項驗證
添加驗證的時候只能用 OptionsBuilder API。
現在將 HomeController 的 ctor 修改爲如下:
#region [ApiController]
private readonly MyOptions _options;
public HomeController(IOptionsSnapshot<MyOptions> options)
{
_options = options.Value;
}
[HttpGet]
public MyOptions Index() { return _options; }
#endregion
添加選項驗證有三種方法:
- 直接註冊驗證函數,調用 Validate
- 使用 Microsoft.Extensions.Options.DataAnnotations 基於數據註釋驗證
- 實現
IValidateOptions<TOptions>
函數
1) 使用Validate 方法驗證
#region ConfigureServices
services.AddOptions<MyOptions>().Configure(options =>
{
Configuration.Bind(options);
}).Validate(options =>
{
return options.Option2 > 0;
}, "Option2 必須大於0");
#endregion
因爲配置文件中 Option2 等於 -1 所以當我們訪問 /Home/Index 時:
可以看到報錯的內容爲我們填寫的 Validate 的第二個參數。
2) 基於數據註釋的驗證
#region ConfigureServices
services.AddOptions<MyOptions>().Configure(options =>
{
Configuration.Bind(options);
}).ValidateDataAnnotations();
#endregion
#region MyOptions Class
[Range(0, int.MaxValue)]
public int Option2 { get; set; } = 5;
#endregion
因爲配置文件中 Option2 等於 -1 所以當我們訪問 /Home/Index 時:
3) 實現 IValidateOptions 進行驗證
首先我們需要實現 IValidateOptions :
#region IValidateOptions
public class MyValidateOptions : IValidateOptions<MyOptions>
{
public ValidateOptionsResult Validate(string name, MyOptions options)
{
if (options.Option2 <= 0)
{
return ValidateOptionsResult.Fail("Option2 必須大於0");
}
else
{
return ValidateOptionsResult.Success;
}
}
}
#endregion
#region ConfigureServices
services.AddOptions<MyOptions>().Configure(options =>
{
Configuration.Bind(options);
}).Services.AddSingleton<IValidateOptions<MyOptions>, MyValidateOptions>();
#endregion
因爲配置文件中 Option2 等於 -1 所以當我們訪問 /Home/Index 時:
十一、在啓動期間訪問選項
可用於 Startup.Configure 中,因爲在 Configure 方法執行之前已生成服務
public void Configure(IApplicationBuilder app,
IOptionsMonitor<MyOptions> optionsAccessor)
{
var option1 = optionsAccessor.CurrentValue.Option1;
}