.Net Core配置Configuration具體實(shí)現(xiàn)
最近又研究了一下.NetCore配置選項(xiàng)的源碼實(shí)現(xiàn),又學(xué)習(xí)到了不少東西。這篇文章先寫一下IConfiguration的學(xué)習(xí)成果,Options的后面補(bǔ)上
核心類
- ConfigurationBuilder:IConfigurationBuilder (構(gòu)建IConfiguration)
- IConfigurationSource (配置數(shù)據(jù)來源)
- IConfigurationProvider (將配置源的原始結(jié)構(gòu)轉(zhuǎn)為為IDictionary<string, string>)
- ConfigurationRoot:IConfigurationRoot:IConfiguration (配置根節(jié)點(diǎn))
構(gòu)建
ConfigurationBuilder
下面是ConfigurationBuilder中的主要代碼
可以看到ConfigurationBuilder的主要功能就是配置數(shù)據(jù)源到集合中
在Build時(shí)依次調(diào)用IConfigurationSource的Build函數(shù),并將返回的IConfigurationProvider加入到List中
最后用IConfigurationProvider的集合構(gòu)建一個(gè)ConfigurationRoot對(duì)象
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,我這里將其合為一個(gè)了
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的作用就是配置數(shù)據(jù)源,并不解析數(shù)據(jù)。
解析數(shù)據(jù)源的功能由 IConfigurationProvider完成
ConfigurationProvider
下面為IConfigurationProvider接口定義的5個(gè)函數(shù)
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是一個(gè)抽象類,繼承了IConfigurationProvider接口
在新建Provider時(shí)一般都會(huì)選擇直接繼承ConfigurationProvider,接下來看一下ConfigurationProvider的幾個(gè)核心方法
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函數(shù)負(fù)責(zé)從源數(shù)據(jù)讀取數(shù)據(jù)然后給字典Data賦值
- ConfigurationProvider將數(shù)據(jù)存儲(chǔ)在字典Data中,增加修改都是對(duì)字典的操作
- 每個(gè)ConfigurationProvider都會(huì)生成一個(gè)IChangeToken,在OnReload函數(shù)被調(diào)用時(shí)生成新的Token,并調(diào)用原Token的OnReload函數(shù)
ConfigurationRoot
在ConfigurationBuilder的Build函數(shù)中,我們生成了一個(gè)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;
上面的代碼也對(duì)部分地方進(jìn)行了簡化??梢钥吹紺onfigurationRoot在生成時(shí)主要就做了兩件事
- 1.調(diào)用Provider的Load函數(shù),這會(huì)給Provider的Data賦值
- 2.讀取Provider的ReloadToken,每個(gè)Provider的Reload事件都會(huì)觸發(fā)ConfigurationRoot自己的ReloadToken的Reload事件
至此配置的數(shù)據(jù)源構(gòu)建這塊就分析完啦!
查詢
常規(guī)的配置查詢有兩種基本方式 :索引器和GetSection(string key)
其余的GetValue等等都是一些擴(kuò)展方法,本篇文章不對(duì)此進(jìn)行展開研究
索引器
索引器的查詢執(zhí)行的方式是倒敘查詢所有的Provider,然后調(diào)用Provider的TryGet函數(shù),在查詢時(shí)重名的Key,最后加入的會(huì)生效。
賦值則是依次調(diào)用每個(gè)Provider的Set函數(shù)
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會(huì)生成一個(gè)ConfigurationSection對(duì)象
而ConfigurationSection在讀取/設(shè)置值時(shí)實(shí)際上就是對(duì)查詢的Key用:拼接,然后調(diào)用IConfigurationRoot(_root)的賦值或查詢函數(shù)
關(guān)于Configuration的配置和讀取的知識(shí)點(diǎn)大概就是以上這些了,還有更深入的涉及到對(duì)象的綁定這一塊Get<> Bind<> GetChildren()等,比較難讀,要一行一行代碼看,以后有時(shí)間可能再研究一下
最后貼上一個(gè)從數(shù)據(jù)加載配置源并動(dòng)態(tài)更新的小例子
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($"讀取配置時(shí)間:{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($"更新數(shù)據(jù)庫數(shù)據(jù)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);
}
}
到此這篇關(guān)于.Net Core配置Configuration具體實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān).Net Core Configuration內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用.NET?6開發(fā)TodoList應(yīng)用之引入數(shù)據(jù)存儲(chǔ)的思路詳解
在這篇文章中,我們僅討論如何實(shí)現(xiàn)數(shù)據(jù)存儲(chǔ)基礎(chǔ)設(shè)施的引入,具體的實(shí)體定義和操作后面專門來說。對(duì).NET?6開發(fā)TodoList引入數(shù)據(jù)存儲(chǔ)相關(guān)知識(shí)感興趣的朋友一起看看吧2021-12-12
解決Visual Studio 2005 無法顯示設(shè)計(jì)視圖的方法
解決Visual Studio 2005 無法顯示設(shè)計(jì)視圖的方法...2007-04-04
MongoDB.Net工具庫MongoRepository使用方法詳解
這篇文章主要為大家詳細(xì)介紹了MongoDB.Net工具庫MongoRepository的使用方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-01-01
在ASP.Net中實(shí)現(xiàn)flv視頻轉(zhuǎn)換的代碼
在ASP.Net中實(shí)現(xiàn)flv視頻轉(zhuǎn)換的代碼...2007-09-09
ASP.NET The system cannot find the file specified解決辦法
這篇文章主要介紹了ASP.NET The system cannot find the file specified解決辦法的相關(guān)資料,需要的朋友可以參考下2016-11-11
Entity Framework Core使用控制臺(tái)程序生成數(shù)據(jù)庫表
這篇文章介紹了Entity Framework Core使用控制臺(tái)程序生成數(shù)據(jù)庫表的方法,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-03-03
3分鐘快速學(xué)會(huì)在ASP.NET Core MVC中如何使用Cookie
這篇文章主要給大家介紹了關(guān)于如何通過3分鐘快速學(xué)會(huì)在ASP.NET Core MVC中使用Cookie的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用ASP.NET具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12
ASP.NET編譯執(zhí)行常見錯(cuò)誤及解決方法匯總
ASP.NET編譯執(zhí)行常見錯(cuò)誤及解決方法匯總,開發(fā)asp.net的朋友可以參考下。方便以后解決一些錯(cuò)誤。2011-12-12

