目录
- 核心类
- 构建
- 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!