目录
- 由代码开始
- 定义一个用户配置选项
- 定义json配置文件:myconfig.json
- 创建servicecollection
- 示例代码
- 代码运行结果
- 通过运行代码得到的结论
- 问题
- 配合源码解决疑惑
- configure注入
- optionsmanager
- optionsfactory
- namedconfigurefromconfigurationoptions
- validateoptions
- 结论
.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内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!