目录
- 核心类
- 构建
- configurationbuilder
- iconfigurationsource
- configurationprovider
- configurationroot
- 查询
- 索引器
- getsection
- dbconfiguration示例
最近又研究了一下.netcore配置选项的源码实现,又学习到了不少东西。这篇文章先写一下iconfiguration的学习成果,options的后面补上
核心类
- configurationbuilder:iconfigurationbuilder (构建iconfiguration)
- iconfigurationsource (配置数据来源)
- iconfigurationprovider (将配置源的原始结构转为为idictionary<string, string>)
- configurationroot:iconfigurationroot:iconfiguration (配置根节点)
构建
configurationbuilder
下面是configurationbuilder中的主要代码
可以看到configurationbuilder的主要功能就是配置数据源到集合中
在build时依次调用iconfigurationsource的build函数,并将返回的iconfigurationprovider加入到list中
最后用iconfigurationprovider的集合构建一个configurationroot对象
public ilist<iconfigurationsource> sources = new list<iconfigurationsource>(); public iconfigurationbuilder add(iconfigurationsource source) { sources.add(source); return this; } public iconfigurationroot build() { list<iconfigurationprovider> list = new list<iconfigurationprovider>(); foreach (iconfigurationsource source in sources) { iconfigurationprovider item = source.build(this); list.add(item); } return new configurationroot(list); }
iconfigurationsource
public class environmentvariablesconfigurationsource : iconfigurationsource { public string prefix; public iconfigurationprovider build(iconfigurationbuilder builder) { return new environmentvariablesconfigurationprovider(prefix); } public environmentvariablesconfigurationsource() { } } public class commandlineconfigurationsource : iconfigurationsource { public idictionary<string, string> switchmappings; public ienumerable<string> args; public iconfigurationprovider build(iconfigurationbuilder builder) { return new commandlineconfigurationprovider(args, switchmappings); } public commandlineconfigurationsource() { } } //jsonconfigurationsource继承自fileconfigurationsource,我这里将其合为一个了 public abstract class jsonconfigurationsource : iconfigurationsource { public ifileprovider fileprovider { get; set; } public string path { get; set; } public bool optional { get; set; } public bool reloadonchange { get; set; } public int reloaddelay { get; set; } = 250; public action<fileloadexceptioncontext> onloadexception { get; set; } public iconfigurationprovider build(iconfigurationbuilder builder) { fileprovider = fileprovider ?? builder.getfileprovider(); onloadexception = onloadexception ?? builder.getfileloadexceptionhandler(); return new jsonconfigurationprovider(this); } public void resolvefileprovider() { if (fileprovider == null && !string.isnullorempty(path) && system.io.path.ispathrooted(path)) { string directoryname = system.io.path.getdirectoryname(path); string text = system.io.path.getfilename(path); while (!string.isnullorempty(directoryname) && !directory.exists(directoryname)) { text = system.io.path.combine(system.io.path.getfilename(directoryname), text); directoryname = system.io.path.getdirectoryname(directoryname); } if (directory.exists(directoryname)) { fileprovider = new physicalfileprovider(directoryname); path = text; } } } }
上面展示了比较常用的三种configurationsource,代码都比较简单。
也很容易看出来configurationsource的作用就是配置数据源,并不解析数据。
解析数据源的功能由 iconfigurationprovider完成
configurationprovider
下面为iconfigurationprovider接口定义的5个函数
public interface iconfigurationprovider { bool tryget(string key, out string value); void set(string key, string value); ichangetoken getreloadtoken(); void load(); ienumerable<string> getchildkeys(ienumerable<string> earlierkeys, string parentpath); }
configurationprovider是一个抽象类,继承了iconfigurationprovider接口
在新建provider时一般都会选择直接继承configurationprovider,接下来看一下configurationprovider的几个核心方法
public abstract class configurationprovider : iconfigurationprovider { private configurationreloadtoken _reloadtoken = new configurationreloadtoken(); protected idictionary<string, string> data= new dictionary<string, string>(stringcomparer.ordinalignorecase); public virtual bool tryget(string key, out string value)=>data.trygetvalue(key, out value); public virtual void set(string key, string value)=>data[key] = value; public virtual void load(){} public ichangetoken getreloadtoken() { return _reloadtoken; } protected void onreload() { configurationreloadtoken configurationreloadtoken = interlocked.exchange(ref _reloadtoken, new configurationreloadtoken()); configurationreloadtoken.onreload(); }
可以推测出:
- load函数负责从源数据读取数据然后给字典data赋值
- configurationprovider将数据存储在字典data中,增加修改都是对字典的操作
- 每个configurationprovider都会生成一个ichangetoken,在onreload函数被调用时生成新的token,并调用原token的onreload函数
configurationroot
在configurationbuilder的build函数中,我们生成了一个configurationroot,并给他传递了所有的configrationprovider列表,下面我们看看他用我们的provider都做了啥吧
private configurationreloadtoken _changetoken = new configurationreloadtoken(); public configurationroot(ilist<iconfigurationprovider> providers) { _providers = providers; _changetokenregistrations = new list<idisposable>(providers.count); foreach (iconfigurationprovider p in providers) { p.load(); changetoken.onchange(p.getreloadtoken, delegate{ var oldtoken=interlocked.exchange(ref _changetoken, new configurationreloadtoken()); oldtoken.onreload(); }) } } public ichangetoken getreloadtoken()=>_changetoken;
上面的代码也对部分地方进行了简化。可以看到configurationroot在生成时主要就做了两件事
- 1.调用provider的load函数,这会给provider的data赋值
- 2.读取provider的reloadtoken,每个provider的reload事件都会触发configurationroot自己的reloadtoken的reload事件
至此配置的数据源构建这块就分析完啦!
查询
常规的配置查询有两种基本方式 :索引器和getsection(string key)
其余的getvalue等等都是一些扩展方法,本篇文章不对此进行展开研究
索引器
索引器的查询执行的方式是倒叙查询所有的provider,然后调用provider的tryget函数,在查询时重名的key,最后加入的会生效。
赋值则是依次调用每个provider的set函数
public string this[string key] { get { for (int num = _providers.count - 1; num >= 0; num--) { if (_providers[num].tryget(key, out var value)) { return value; } } return null; } set { foreach (iconfigurationprovider provider in _providers) { provider.set(key, value); } } }
getsection
public iconfigurationsection getsection(string key) { return new configurationsection(this, key); } public class configurationsection : iconfigurationsection, iconfiguration { private readonly iconfigurationroot _root; private readonly string _path; private string _key; public string value { get { return _root[path]; } set { _root[path] = value; } } //configurationpath.combine = string.join(":",paramlist); public string this[string key] { get { return _root[configurationpath.combine(path, key)]; } set { _root[configurationpath.combine(path, key)] = value; } } public configurationsection(iconfigurationroot root, string path) { _root = root; _path = path; } public iconfigurationsection getsection(string key) { return _root.getsection(configurationpath.combine(path, key)); } public ienumerable<iconfigurationsection> getchildren() { return _root.getchildrenimplementation(path); } public ichangetoken getreloadtoken() { return _root.getreloadtoken(); } }
可以看到getsection会生成一个configurationsection对象
而configurationsection在读取/设置值时实际上就是对查询的key用:拼接,然后调用iconfigurationroot(_root)的赋值或查询函数
关于configuration的配置和读取的知识点大概就是以上这些了,还有更深入的涉及到对象的绑定这一块get<> bind<> getchildren()等,比较难读,要一行一行代码看,以后有时间可能再研究一下
最后贴上一个从数据加载配置源并动态更新的小例子
dbconfiguration示例
public void run() { var builder = new configurationbuilder(); var dataprovider = new dbdataprovider(); builder.sources.add(new dbconfigurationsource() { dataprovider = dataprovider, reloadonchange = true, table = "config" }); iconfigurationroot config = builder.build(); console.writeline(config["time"]); task.run(() => { while (true) { thread.sleep(2000); dataprovider.update("config"); console.writeline($"读取配置时间:{config["time"]}"); } }); thread.sleep(20000); } public class dbconfigurationprovider : configurationprovider { private dbconfigurationsource source { get; } public dbconfigurationprovider(dbconfigurationsource source) { source = source; } public override void load() { if (source.reloadonchange) { changetoken.onchange(() => source.dataprovider.watch(source.table), loaddata); } loaddata(); } private void loaddata() { var data = source.dataprovider.getdata(source.table); load(data); onreload(); } public void load(dictionary<string, object> data) { var dic = new sorteddictionary<string, string>(stringcomparer.ordinalignorecase); foreach (var element in data) { dic.add(element.key, element.value?.tostring()); } base.data = dic; } } public class dbconfigurationsource : iconfigurationsource { public dbdataprovider dataprovider { get; set; } public string table { get; set; } public bool reloadonchange { get; set; } public bool optional { get; set; } public dbconfigurationsource() { } public iconfigurationprovider build(iconfigurationbuilder builder) { return new dbconfigurationprovider(this); } } public class dbdataprovider { private concurrentdictionary<string, cancellationtokensource> tabletoken = new concurrentdictionary<string, cancellationtokensource>(); public dbdataprovider() { } public dictionary<string, object> getdata(string table) { switch (table) { case "config": return getconfig(); } return new dictionary<string, object>(); } public void update(string table) { console.writeline($"更新数据库数据table:{table}"); if (tabletoken.trygetvalue(table, out cancellationtokensource cts)) { var oldcts = cts; tabletoken[table] = new cancellationtokensource(); oldcts.cancel(); } } private dictionary<string, object> getconfig() { var valuedic = new dictionary<string, object>(); valuedic.tryadd("time", datetime.now.tostring()); valuedic.tryadd("weather", "windy"); valuedic.tryadd("people_number:male", 100); valuedic.tryadd("people_number:female", 150); return valuedic; } public ichangetoken watch(string table) { var cts = tabletoken.getoradd(table, x => new cancellationtokensource()); return new cancellationchangetoken(cts.token); } }
到此这篇关于.net core配置configuration具体实现的文章就介绍到这了,更多相关.net core configuration内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!