.NetCore选项配置中的IOptions,IOptionsM
2020-10-23 本文已影响0人
AlexWillBeGood
ASP.NET Core中可以使用Options模式,从而能够使用强类型的类来表达配置项,这也是实现配置的最佳实践。
对外提供的配置项接口有IOptions<TOptions>,IOptionsMonitor<TOptions>,IOptionsSnapshot<TOptions>,这三种适用于不同的场景:
- IOptions<TOptions>适用于一般配置场景
- IOptionsMonitor<TOptions>适用于需要热更新配置并且全局统一的场景
- IOptionsSnapshot<TOptions>适用于需要热更新配置并且服务内统一的场景
举个例子:
创建一个Console程序,并添加一个配置文件appsettings.json
{
"TestOptions": {
"Name": "0"
}
}
并且引入以下扩展包:

Main函数中写入
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; }
}
其实主要就是做了三步:
- 创建TestOptions类用于绑定配置,容器ServiceProvider,基于appsetting.json的配置Configuration。
- 分别在不同子容器中,代码修改配置,打印当前配置信息。
- 手动在appsettings.json中修改信息,在子容器中打印当前配置信息。
最终的结果是:

分析得出:
- 使用代码修改配置时候,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获取到的服务都是互不影响的,因此在例子中没有改变,同时因为每一次创建都重新去读当前配置文件(snapshot),因为达到更新的目的。
至于IOptionsMonitor单例能够热更新配置,可以查看其实现代码:
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>