.Net Core 中选项Options的具体实现
.NetCore的配置选项建议结合在一起学习,不了解.NetCore 配置Configuration的同学可以看下我的上一篇文章 [.Net Core配置Configuration具体实现]
由代码开始
定义一个用户配置选项
public class UserOptions { private string instanceId; private static int index = 0; public UserOptions() { instanceId = (++index).ToString("00"); Console.WriteLine($"Create UserOptions Instance:{instanceId}"); } public string Name { get; set; } public int Age { get; set; } public override string ToString() => $"Name:{Name} Age:{Age} Instance:{instanceId} "; } public class UserOptions2 { public string Name { get; set; } public int Age { get; set; } public override string ToString() => $" Name:{Name} Age:{Age}"; }
定义json配置文件:myconfig.json
{ "UserOption": { "Name": "ConfigName-zhangsan", "Age": 666 } }
创建ServiceCollection
services = new ServiceCollection(); var configBuilder = new ConfigurationBuilder().AddInMemoryCollection().AddJsonFile("myconfig.json", true, true); var iconfiguration = configBuilder.Build(); services.AddSingleton<IConfiguration>(iconfiguration);
示例代码
services.Configure<UserOptions>(x => { x.Name = "张三"; x.Age = new Random().Next(1, 10000); }); services.AddOptions<UserOptions2>().Configure<IConfiguration>((x, config) => { x.Name = config["UserOption:Name"]; x.Age = 100; }); ; services.PostConfigure<UserOptions>(x => { x.Name = x.Name + "Post"; x.Age = x.Age; }); services.Configure<UserOptions>("default", x => { x.Name = "Default-张三"; x.Age = new Random().Next(1, 10000); }); services.Configure<UserOptions>("config", configuration.GetSection("UserOption")); using (var provider = services.BuildServiceProvider()) { using (var scope1 = provider.CreateScope()) { PrintOptions(scope1, "Scope1"); } //修改配置文件 Console.WriteLine(string.Empty); Console.WriteLine("修改配置文件"); var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "myconfig.json"); File.WriteAllText(filePath, "{\"UserOption\": { \"Name\": \"ConfigName-lisi\", \"Age\": 777}}"); //配置文件的change回调事件需要一定时间执行 Thread.Sleep(300); Console.WriteLine(string.Empty); using (var scope2 = provider.CreateScope()) { PrintOptions(scope2, "Scope2"); } Console.WriteLine(string.Empty); using (var scope3 = provider.CreateScope()) { PrintOptions(scope3, "Scope3"); } } static void PrintOptions(IServiceScope scope, string scopeName) { var options1 = scope.ServiceProvider.GetService<IOptions<UserOptions>>(); Console.WriteLine($"手动注入读取,IOptions,{scopeName}-----{ options1.Value}"); var options2 = scope.ServiceProvider.GetService<IOptionsSnapshot<UserOptions>>(); Console.WriteLine($"配置文件读取,IOptionsSnapshot,{scopeName}-----{ options2.Value}"); var options3 = scope.ServiceProvider.GetService<IOptionsSnapshot<UserOptions>>(); Console.WriteLine($"配置文件根据名称读取,IOptionsSnapshot,{scopeName}-----{ options3.Get("config")}"); var options4 = scope.ServiceProvider.GetService<IOptionsMonitor<UserOptions>>(); Console.WriteLine($"配置文件读取,IOptionsMonitor,{scopeName}-----{ options4.CurrentValue}"); var options5 = scope.ServiceProvider.GetService<IOptionsMonitor<UserOptions>>(); Console.WriteLine($"配置文件根据名称读取,IOptionsMonitor,{scopeName}-----{options5.Get("config")}"); var options6 = scope.ServiceProvider.GetService<IOptions<UserOptions2>>(); Console.WriteLine($"Options2-----{options6.Value}"); }
代码运行结果
Create UserOptions Instance:01
手动注入读取,IOptions,Scope1----- Name:张三Post Age:6575 Instance:01
Create UserOptions Instance:02
配置文件读取,IOptionsSnapshot,Scope1----- Name:张三Post Age:835 Instance:02
Create UserOptions Instance:03
配置文件根据名称读取,IOptionsSnapshot,Scope1----- Name:ConfigName-zhangsan Age:666 Instance:03
Create UserOptions Instance:04
配置文件读取,IOptionsMonitor,Scope1----- Name:张三Post Age:1669 Instance:04
Create UserOptions Instance:05
配置文件根据名称读取,IOptionsMonitor,Scope1----- Name:ConfigName-zhangsan Age:666 Instance:05
Options2----- Name:ConfigName-zhangsan Age:100修改配置文件
Create UserOptions Instance:06手动注入读取,IOptions,Scope2----- Name:张三Post Age:6575 Instance:01
Create UserOptions Instance:07
配置文件读取,IOptionsSnapshot,Scope2----- Name:张三Post Age:5460 Instance:07
Create UserOptions Instance:08
配置文件根据名称读取,IOptionsSnapshot,Scope2----- Name:ConfigName-lisi Age:777 Instance:08
配置文件读取,IOptionsMonitor,Scope2----- Name:张三Post Age:1669 Instance:04
配置文件根据名称读取,IOptionsMonitor,Scope2----- Name:ConfigName-lisi Age:777 Instance:06
Options2----- Name:ConfigName-zhangsan Age:100手动注入读取,IOptions,Scope3----- Name:张三Post Age:6575 Instance:01
Create UserOptions Instance:09
配置文件读取,IOptionsSnapshot,Scope3----- Name:张三Post Age:5038 Instance:09
Create UserOptions Instance:10
配置文件根据名称读取,IOptionsSnapshot,Scope3----- Name:ConfigName-lisi Age:777 Instance:10
配置文件读取,IOptionsMonitor,Scope3----- Name:张三Post Age:1669 Instance:04
配置文件根据名称读取,IOptionsMonitor,Scope3----- Name:ConfigName-lisi Age:777 Instance:06
Options2----- Name:ConfigName-zhangsan Age:100
通过运行代码得到的结论
- Options可通过手动初始化配置项配置(可在配置时读取依赖注入的对象)、或通过IConfiguration绑定配置
- PostConfiger可在Configer基础上继续配置
- 可通过IOptionsSnapshot或IOptionsMonitor根据配置名称读取配置项,未指定名称读取第一个注入的配置
- IOptions和IOptionsMonitor生命周期为Singleton,IOptionsSnapshot生命周期为Scope
- IOptionsMonitor可监听到配置文件变动去动态更新配置项
问题
- IOptions,IOptionsSnapshot,IOptionsMonitor 如何/何时注入、初始化
- Options指定名称时内部是如何设置的
- Options如何绑定的IConfiguration
- IOptionsMonitor是如何同步配置文件变动的
配合源码解决疑惑
Configure注入
public static IServiceCollection Configure<TOptions>(this IServiceCollection services, Action<TOptions> configureOptions) where TOptions : class { return services.Configure(Microsoft.Extensions.Options.Options.DefaultName, configureOptions); } public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, Action<TOptions> configureOptions) where TOptions : class { services.AddOptions(); services.AddSingleton((IConfigureOptions<TOptions>)new ConfigureNamedOptions<TOptions>(name, configureOptions)); return services; } public static IServiceCollection AddOptions(this IServiceCollection 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; }
通过上面的源码可以发现,Options相关类是在AddOptions中注入的,具体的配置项在Configure中注入。
如果不指定Configure的Name,也会有个默认的Name=Microsoft.Extensions.Options.Options.DefaultName
那么我们具体的配置项存到哪里去了呢,在ConfigureNamedOptions这个类中,在Configer函数调用时,只是把相关的配置委托存了起来:
public ConfigureNamedOptions(string name, Action<TOptions> action) { Name = name; Action = action; }
OptionsManager
private readonly ConcurrentDictionary<string, Lazy<TOptions>> _cache = new ConcurrentDictionary<string, Lazy<TOptions>>(StringComparer.Ordinal); public TOptions Value => Get(Options.DefaultName); public virtual TOptions Get(string name) { name = name ?? Options.DefaultName; return _cache.GetOrAdd(name, () => _factory.Create(name)); }
OptionsManager实现相对较简单,在查询时需要执行Name,如果为空就用默认的Name,如果缓存没有,就用Factory创建一个,否则就读缓存中的选项。
IOptions和IOptionsSnapshot的实现类都是OptionsManager,只是生命周期不同。
OptionsFactory
那么OptionsFactory又是如何创建Options的呢?我们看一下他的构造函数,构造函数将所有Configure和PostConfigure的初始化委托都通过构造函数保存在内部变量中
public OptionsFactory(IEnumerable<IConfigureOptions<TOptions>> setups, IEnumerable<IPostConfigureOptions<TOptions>> postConfigures) { _setups = setups; _postConfigures = postConfigures; }
接下来看Create(有删改,与本次研究无关的代码没有贴出来):
public TOptions Create(string name) { //首先创建对应Options的实例 TOptions val = Activator.CreateInstance<TOptions>(); //循环所有的配置项,依次执行,如果对同一个Options配置了多次,最后一次的赋值生效 foreach (IConfigureOptions<TOptions> setup in _setups) { var configureNamedOptions = setup as IConfigureNamedOptions<TOptions>; if (configureNamedOptions != null) { //Configure中会判断传入Name的值与本身的Name值是否相同,不同则不执行Action //这解释了我们一开始的示例中,注入了三个UserOptions,但是在IOptionsSnapshot.Value中获取到的是第一个没有名字的 //因为Value会调用OptionsManager.Get(Options.DefaultName),进而调用Factory的Create(Options.DefaultName) configureNamedOptions.Configure(name, val); } else if (name == Options.DefaultName) { setup.Configure(val); } } //PostConfigure没啥可多说了,名字判断逻辑与Configure一样 foreach (var postConfigure in _postConfigures) { postConfigure.PostConfigure(name, val); } return val; }
NamedConfigureFromConfigurationOptions
IConfiguration配置Options的方式略有不同
对应Configure扩展方法最终调用的代码在Microsoft.Extensions.DependencyInjection.OptionsConfigurationServiceCollectionExtensions这个类中
public static IServiceCollection Configure<TOptions>(this IServiceCollection services, string name, IConfiguration config, Action<BinderOptions> configureBinder) where TOptions : class { services.AddOptions(); services.AddSingleton((IOptionsChangeTokenSource<TOptions>)new ConfigurationChangeTokenSource<TOptions>(name, config)); return services.AddSingleton((IConfigureOptions<TOptions>)new NamedConfigureFromConfigurationOptions<TOptions>(name, config, configureBinder)); }
扩展方法里又注入了一个IOptionsChangeTokenSource,这个类的作用是提供一个配置文件变动监听的Token
同时将IConfigureOptions实现类注册成了NamedConfigureFromConfigurationOptions
NamedConfigureFromConfigurationOptions继承了ConfigureNamedOptions,在构造函数中用IConfiguration.Bind实现了生成Options的委托
public NamedConfigureFromConfigurationOptions(string name, IConfiguration config, Action<BinderOptions> configureBinder) : base(name, (Action<TOptions>)delegate(TOptions options) { config.Bind(options, configureBinder); })
所以在Factory的Create函数中,会调用IConfiguration的Bind函数
由于IOptionsSnapshot生命周期是Scope,在配置文件变动后新的Scope中会获取最新的Options
ValidateOptions
OptionsBuilder还包含了一个Validate函数,该函数要求传入一个Func<TOptions,bool>的委托,会注入一个单例的ValidateOptions对象。
在OptionsFactory构建Options的时候会验证Options的有效性,验证失败会抛出OptionsValidationException异常
对于ValidateOptions和PostConfigureOptions都是构建Options实例时需要用到的主要模块,不过使用和内部实现都较为简单,应用场景也不是很多,本文就不对这两个类多做介绍了
结论
在Configure扩展函数中会首先调用AddOptions函数
IOptions,IOptionsSnapshot,IOptionsMonitor都是在AddOptions函数中注入的
Configure配置的选项配置委托最终会保存到ConfigureNamedOptions或NamedConfigureFromConfigurationOptions
IOptions和IOptionsSnapshot的实现类为OptionsManager
OptionsManager通过OptionsFactory创建Options的实例,并会以Name作为键存到字典中缓存实例
OptionsFactory会通过反射创建Options的实例,并调用ConfigureNamedOptions中的委托给实例赋值
现在只剩下最后一个问题了,OptionsMonitor是如何动态更新选项的呢?
其实前面的讲解中已经提到了一个关键的接口IOptionsChangeTokenSource,这个接口提供一个IChangeToken,通过ChangeToken监听这个Token就可以监听到文件的变动,我们来看下OptionsMonitor是否是这样做的吧!
//构造函数 public OptionsMonitor(IOptionsFactory<TOptions> factory, IEnumerable<IOptionsChangeTokenSource<TOptions>> sources, IOptionsMonitorCache<TOptions> cache) { _factory = factory; _sources = sources; _cache = cache; //循环属于TOptions的所有IChangeToken foreach (IOptionsChangeTokenSource<TOptions> source in _sources) { ChangeToken.OnChange(() => source.GetChangeToken(), delegate(string name) { //清除缓存 name = name ?? Options.DefaultName; _cache.TryRemove(name); }, source.Name); } } public virtual TOptions Get(string name) { name = name ?? Options.DefaultName; return _cache.GetOrAdd(name, () => _factory.Create(name)); }
果然是这样的吧!
到此这篇关于.Net Core 中选项Options的具体实现的文章就介绍到这了,更多相关.Net Core Options内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!
相关文章
- 这篇文章主要为大家详细介绍了ASP.NET购物车的实现过程,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-09-22
- 这篇文章主要介绍了.NET Core下使用Kafka的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-09-22
- 在开发过程中,使用Visual Studio的断点调试功能可以很方便帮我们调试发现程序存在的错误,同样Visual Studio也支持对SQL Server里面的存储过程进行调试,下面就让我们看看具体的调试方法。...2021-09-22
- 这篇文章主要介绍了Win10 IIS 安装及.net 4.5及Win10安装IIS并配置ASP.NET 4.0的方法,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下...2021-09-22
- 这篇文章主要介绍了详解.NET Core 3.0 里新的JSON API,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-09-22
- 这篇文章主要介绍了.net数据库操作框架SqlSugar的简单入门,帮助大家更好的理解和学习使用.net技术,感兴趣的朋友可以了解下...2021-09-22
ASP.NET Core根据环境变量支持多个 appsettings.json配置文件
这篇文章主要介绍了ASP.NET Core根据环境变量支持多个 appsettings.json配置文件,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-09-22- 这篇文章主要介绍了记一次EFCore类型转换错误及解决方案,帮助大家更好的理解和学习使用asp.net core,感兴趣的朋友可以了解下...2021-09-22
- ZXing是一个开放源码的,用Java实现的多种格式的1D/2D条码图像处理库,它包含了联系到其他语言的端口。这篇文章主要给大家介绍了.NET C#利用ZXing生成、识别二维码/条形码的方法,文中给出了详细的示例代码,有需要的朋友们可以参考借鉴。...2020-06-25
详解ASP.NET Core 中基于工厂的中间件激活的实现方法
这篇文章主要介绍了ASP.NET Core 中基于工厂的中间件激活的实现方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-09-22- 这篇文章主要介绍了C#使用Ado.Net更新和添加数据到Excel表格的方法,较为详细的分析了OLEDB的原理与使用技巧,可实现较为方便的操作Excel数据,需要的朋友可以参考下...2020-06-25
asp.net通过消息队列处理高并发请求(以抢小米手机为例)
这篇文章主要介绍了asp.net通过消息队列处理高并发请求(以抢小米手机为例),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-09-22ASP.NET 2.0中的数据操作:使用两个DropDownList过滤的主/从报表
在前面的指南中我们研究了如何显示一个简单的主/从报表, 该报表使用DropDownList和GridView控件, DropDownList填充类别,GridView显示选定类别的产品. 这类报表用于显示具有...2016-05-19ASP.NET单选按钮控件RadioButton常用属性和方法介绍
RadioButton又称单选按钮,其在工具箱中的图标为 ,单选按钮通常成组出现,用于提供两个或多个互斥选项,即在一组单选钮中只能选择一个...2021-09-22详解.NET Core 使用HttpClient SSL请求出错的解决办法
这篇文章主要介绍了.NET Core 使用HttpClient SSL请求出错的解决办法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2021-09-22- 这篇文章主要介绍了Python调用.NET库的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-05-09
ASP.NET中iframe框架点击左边页面链接 右边显示链接页面内容
这篇文章主要介绍了ASP.NET中iframe框架点击左边页面链接,右边显示链接页面内容的实现代码,感兴趣的小伙伴们可以参考一下...2021-09-22- ASP.NET Web API具有与ASP.NET MVC类似的编程方式,ASP.NET Web API不仅仅具有一个完全独立的消息处理管道,而且这个管道比为ASP.NET MVC设计的管道更为复杂,功能也更为强大。下面创建一个简单的Web API项目,需要的朋友可以参考下...2021-09-22
- 这篇文章主要介绍了ASP.NET连接MySql数据库的2个方法及示例,使用的是MySQL官方组件和ODBC.NET,需要的朋友可以参考下...2021-09-22
- 这篇文章主要介绍了Asp.Net使用Bulk实现批量插入数据的方法,对于进行asp.net数据库程序设计非常有借鉴价值,需要的朋友可以参考下...2021-09-22