ASP.NET Core中可以使用Options模式,從而能夠使用強類型的類來表達配置項娱俺,這也是實現(xiàn)配置的最佳實踐废麻。
對外提供的配置項接口有IOptions<TOptions>,IOptionsMonitor<TOptions>,IOptionsSnapshot<TOptions>,這三種適用于不同的場景:
- IOptions<TOptions>適用于一般配置場景
- IOptionsMonitor<TOptions>適用于需要熱更新配置并且全局統(tǒng)一的場景
- IOptionsSnapshot<TOptions>適用于需要熱更新配置并且服務內統(tǒng)一的場景
舉個例子:
創(chuàng)建一個Console程序油宜,并添加一個配置文件appsettings.json
{
"TestOptions": {
"Name": "0"
}
}
并且引入以下擴展包:
image.png
Main函數(shù)中寫入
static void Main(string[] args)
{
IConfigurationRoot root = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.Build();
var services = new ServiceCollection();
services.AddOptions();
services.Configure<TestOptions>(root.GetSection("TestOptions"));
var provider = services.BuildServiceProvider();
Console.WriteLine("原始值:");
ShowOptions(provider);
Console.WriteLine("第一次代碼修改:");
//第一次修改(代碼修改)
Change(provider);
ShowOptions(provider);
Console.WriteLine("第二次手動修改:");
//第二次修改(手動修改)
Console.WriteLine("開始修改配置文件");
Console.ReadLine();
ShowOptions(provider);
Console.ReadKey();
}
/// <summary>
/// 顯示所有的選項
/// </summary>
static void ShowOptions(IServiceProvider serviceProvider)
{
using (var scope = serviceProvider.CreateScope())
{
var sp = scope.ServiceProvider;
var options = sp.GetRequiredService<IOptions<TestOptions>>();
var monitor = sp.GetRequiredService<IOptionsMonitor<TestOptions>>();
var snapshot = sp.GetRequiredService<IOptionsSnapshot<TestOptions>>();
Console.WriteLine($"options:{options.Value.name}");
Console.WriteLine($"monitor:{monitor.CurrentValue.name}");
Console.WriteLine($"snapshot:{snapshot.Value.name}");
}
}
/// <summary>
/// 手動去改變
/// </summary>
static void Change(IServiceProvider serviceProvider)
{
using (var scope = serviceProvider.CreateScope())
{
var sp = scope.ServiceProvider;
var options = sp.GetRequiredService<IOptions<TestOptions>>();
var monitor = sp.GetRequiredService<IOptionsMonitor<TestOptions>>();
var snapshot = sp.GetRequiredService<IOptionsSnapshot<TestOptions>>();
options.Value.name = "1";
monitor.CurrentValue.name = "1";
snapshot.Value.name = "1";
}
}
public class TestOptions
{
/// <summary>
/// 名稱
/// </summary>
public string name { get; set; }
}
其實主要就是做了三步:
- 創(chuàng)建TestOptions類用于綁定配置顶吮,容器ServiceProvider悴了,基于appsetting.json的配置Configuration违寿。
- 分別在不同子容器中,代碼修改配置搞莺,打印當前配置信息掂咒。
- 手動在appsettings.json中修改信息,在子容器中打印當前配置信息温圆。
最終的結果是:
image.png
分析得出:
- 使用代碼修改配置時候岁歉,snapshot不變
- 直接修改配置文件時候膝蜈,options不變
可以注意到這句代碼實際上就是注冊這些接口
var services = new ServiceCollection();
services.AddOptions();
查看源碼:
public static IServiceCollection AddOptions(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(OptionsManager<>)));
services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>)));
services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitor<>), typeof(OptionsMonitor<>)));
services.TryAdd(ServiceDescriptor.Transient(typeof(IOptionsFactory<>), typeof(OptionsFactory<>)));
services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitorCache<>), typeof(OptionsCache<>)));
return services;
}
可以看到IOptionsSnapshot注冊生命周期是Scoped饱搏,而IOptions,IOptionsMonitor注冊的生命周期是Singleton,這樣就造成了代碼修改配置备绽,在不同的子容器中坤学,IOptionsSnapshot獲取到的服務都是互不影響的,因此在例子中沒有改變压怠,同時因為每一次創(chuàng)建都重新去讀當前配置文件(snapshot)飞苇,因為達到更新的目的蜗顽。
至于IOptionsMonitor單例能夠熱更新配置雇盖,可以查看其實現(xiàn)代碼:
public OptionsMonitor(IOptionsFactory<TOptions> factory, IEnumerable<IOptionsChangeTokenSource<TOptions>> sources, IOptionsMonitorCache<TOptions> cache)
{
_factory = factory;
_sources = sources;
_cache = cache;
foreach (var source in _sources)
{
var registration = ChangeToken.OnChange(
() => source.GetChangeToken(),
(name) => InvokeChanged(name),
source.Name);
_registrations.Add(registration);
}
}
IOptionsChangeTokenSource<TOptions>使得其能夠熱更新崔挖。
三種類型使用順序
- IOptionsMonitor<TOptions>
- IOptionsSnapshot<TOptions>
- IOptions<TOptions>