.NET6?ConfigurationManager的實現(xiàn)及使用方式
前言
友情提示:建議閱讀本文之前先了解下.Net Core配置體系相關(guān),也可以參考本人之前的文章《.Net Core Configuration源碼探究 》然后對.Net Core的Configuration體系有一定的了解,使得理解起來更清晰。
在.Net6中關(guān)于配置相關(guān)多出一個關(guān)于配置相關(guān)的類ConfigurationManager
,如果大概了解過Minimal API中的WebApplicationBuilder
類相信你肯定發(fā)現(xiàn)了,在Minimal API中的配置相關(guān)屬性Configuration正是ConfigurationManager的對象。ConfigurationManager本身并沒有引入新的技術(shù),也不是一個體系,只是在原來的基礎(chǔ)上進行了進一步的封裝,使得配置體系有了一個新的外觀操作,暫且可以理解為新瓶裝舊酒。本文我們就來了解下ConfigurationManager類,來看下微軟為何在.Net6中會引入這么一個新的操作。
使用方式
關(guān)于.Net6中ConfigurationManager的使用方式,我們先通過簡單的示例演示一下
ConfigurationManager configurationManager = new(); configurationManager.AddJsonFile("appsettings.json",true,reloadOnChange:true); string serviceName = configurationManager["ServiceName"]; Console.WriteLine(serviceName);
當(dāng)然,關(guān)于獲取值得其他方式。比如GetSection、GetChildren相關(guān)方法還是可以繼續(xù)使用的,或者使用Binder擴展包相關(guān)的Get<string>()
、GetValue<NacosOptions>("nacos")
類似的方法也照樣可以使用。那它和之前的.Net Core上的配置使用起來有什么不一樣呢,我們看一下之前配置相關(guān)的使用方式,如下所示
IConfigurationBuilder configurationBuilder = new ConfigurationBuilder().AddJsonFile("appsettings.json"); IConfiguration configuration = configurationBuilder.Build(); string serviceName = configuration["ServiceName"]; Console.WriteLine(serviceName);
這里需要注意的是,如果你是使用ConfigurationManager
或者是IConfiguration
封裝的Helper類相關(guān),并沒有通過框架體系默認注入的時候,一定要注意將其設(shè)置為單例模式
。其實這個很好理解,先不說每次用的時候都去實例化帶來的內(nèi)存CPU啥的三高問題。讀取配置文件本質(zhì)不就是把數(shù)據(jù)讀到內(nèi)存中嗎?內(nèi)存中有一份緩存這就好了,每次都去重新實例去讀本身就是一種不規(guī)范的方式。許多時候如果你實在不知道該定義成什么樣的生命周期,可以參考微軟的實現(xiàn)方式,以ConfigurationManager為例,我們可以參考WebApplicationBuilder類中對ConfigurationManager注冊的生命周期[點擊查看源碼]
public ConfigurationManager Configuration { get; } = new(); //這里注冊為了單例模式 Services.AddSingleton<IConfiguration>(_ => Configuration);
通過上面我們演示的示例可以看出在ConfigurationManager的時候注冊配置和讀取配置相關(guān)都只是使用了這一個類。而在之前的配置體系中,注冊配置需要使用IConfigurationBuilder,然后通過Build方法得到IConfiguration實例,然后讀取是通過IConfiguration實例進行的。本身操作配置的時候IConfigurationBuilder和IConfiguration是滿足單一職責(zé)原則沒問題,像讀取配置這種基礎(chǔ)操作,應(yīng)該是越簡單越好,所以微軟才進一步封裝了ConfigurationManager來簡化配置相關(guān)的操作。
在.Net6中微軟并沒有放棄IConfigurationBuilder和IConfiguration,因為這是操作配置文件的基礎(chǔ)類,微軟只是借助了它們兩個在上面做了進一層封裝而已,這個是需要我們了解的。
源碼探究
上面我們了解了新的ConfigurationManager的使用方式,這里其實我們有疑問了,為什么ConfigurationManager可以進行注冊和讀取操作。上面我提到過ConfigurationManager本身就是新瓶裝舊酒,而且它只是針對原有的配置體系做了一個新的外觀,接下來哦我們就從源碼入手,看一下它的實現(xiàn)方式。
定義入手
首先來看一下ConfigurationManager的的定義,如下所示[點擊查看源碼]
public sealed class ConfigurationManager : IConfigurationBuilder, IConfigurationRoot, IDisposable { }
其實只看它的定義就可以解答我們心中的大部分疑惑了,之所以ConfigurationManager能夠滿足IConfigurationBuilder和IConfigurationRoot這兩個操作的功能是因為它本身就是實現(xiàn)了這兩個接口,集它們的功能于一身了,IConfigurationRoot接口本身就集成自IConfiguration接口。因此如果給ConfigurationManager換個馬甲的話你就會發(fā)現(xiàn)還是原來的配方還是原來的味道
ConfigurationManager configurationManager = new(); IConfigurationBuilder configurationBuilder = configurationManager.AddJsonFile("appsettings.json", true, reloadOnChange: true); //盡管放心的調(diào)用Build完全不影響啥 IConfiguration configuration = configurationBuilder.Build(); string serviceName = configuration["ServiceName"]; Console.WriteLine(serviceName);
這種寫法只是為了更好的看清它的本質(zhì),如果真實操作這么寫,確實有點畫蛇添足了,因為ConfigurationManager本身就是為了簡化我們的操作。
認識IConfigurationBuilder和IConfiguration
通過上面我們了解到ConfigurationManager可以直接注冊過配置文件就可以直接去操作配置文件里的內(nèi)容,這一步是肯定通過轉(zhuǎn)換得到的,畢竟之前的方式我們是通過IConfigurationBuilder的Build操作得到的IConfiguration的實例,那么我們就先來看下原始的方式是如何實現(xiàn)的。這里需要從IConfigurationBuilder的默認實現(xiàn)類ConfigurationBuilder說起,它的實現(xiàn)很簡單[點擊查看源碼]
public class ConfigurationBuilder : IConfigurationBuilder { /// <summary> /// 添加的數(shù)據(jù)源被存放到了這里 /// </summary> public IList<IConfigurationSource> Sources { get; } = new List<IConfigurationSource>(); public IDictionary<string, object> Properties { get; } = new Dictionary<string, object>(); /// <summary> /// 添加IConfigurationSource數(shù)據(jù)源 /// </summary> /// <returns></returns> public IConfigurationBuilder Add(IConfigurationSource source) { if (source == null) { throw new ArgumentNullException(nameof(source)); } Sources.Add(source); return this; } public IConfigurationRoot Build() { //獲取所有添加的IConfigurationSource里的IConfigurationProvider var providers = new List<IConfigurationProvider>(); foreach (var source in Sources) { var provider = source.Build(this); providers.Add(provider); } //用providers去實例化ConfigurationRoot return new ConfigurationRoot(providers); } }
這里我們來解釋一下,其實我們注冊配置相關(guān)的時候比如AddJsonFile()、AddEnvironmentVariables()、AddInMemoryCollection()等等它們其實都是擴展方法,本質(zhì)就是添加IConfigurationSource實例,而IConfigurationBuilder的Build本質(zhì)操作其實就是在IConfigurationSource集合中得到IConfigurationProvider集合,因真正從配置讀取到的數(shù)據(jù)都是包含在IConfigurationProvider實例中的,ConfigurationRoot通過一系列的封裝,讓我們可以更便捷的得到配置里相關(guān)的信息。這就是ConfigurationBuilder的工作方式,也是配置體系的核心原理。
我們既然知道了添加配置的本質(zhì)其實就是IConfigurationBuilder.Add(IConfigurationSource source)
那么我就來看一下ConfigurationManager是如何實現(xiàn)這一步的。我們知道ConfigurationManager實現(xiàn)了IConfigurationBuilder接口,所以必然重寫了IConfigurationBuilder的Add方法,找到源碼位置[點擊查看源碼]
private readonly ConfigurationSources _sources = new ConfigurationSources(this); ; IConfigurationBuilder IConfigurationBuilder.Add(IConfigurationSource source) { _sources.Add(source ?? throw new ArgumentNullException(nameof(source))); return this; }
這里返回了this也就是當(dāng)前ConfigurationManager實例是為了可以進行鏈式編程,ConfigurationSources這個類是個新物種,原來的類叫ConfigurationSource,這里多了個s表明了這是一個集合類,我們就來看看它是個啥操作,找到源碼位置[點擊查看源碼]
/// <summary> /// 本身是一個IConfigurationSource集合 /// </summary> private class ConfigurationSources : IList<IConfigurationSource> { private readonly List<IConfigurationSource> _sources = new(); private readonly ConfigurationManager _config; /// <summary> /// 因為是ConfigurationManager的內(nèi)部類所以傳遞了當(dāng)前ConfigurationManager實例 /// </summary> /// <param name="config"></param> public ConfigurationSources(ConfigurationManager config) { _config = config; } /// <summary> /// 根據(jù)索引獲取其中一個IConfigurationSource實例 /// </summary> /// <returns></returns> public IConfigurationSource this[int index] { get => _sources[index]; set { _sources[index] = value; _config.ReloadSources(); } } public int Count => _sources.Count; public bool IsReadOnly => false; /// <summary> /// 這是重點添加配置源 /// </summary> /// <param name="source"></param> public void Add(IConfigurationSource source) { //給自己的IConfigurationSource集合添加 _sources.Add(source); //調(diào)用了ConfigurationManager的AddSource方法 _config.AddSource(source); } /// <summary> /// 實現(xiàn)IList清除操作 /// </summary> public void Clear() { _sources.Clear(); //這里可以看到ConfigurationManager的ReloadSources方法很重要 //通過名字可以看出是刷新配置數(shù)據(jù)用的 _config.ReloadSources(); } public void Insert(int index, IConfigurationSource source) { _sources.Insert(index, source); _config.ReloadSources(); } public bool Remove(IConfigurationSource source) { var removed = _sources.Remove(source); _config.ReloadSources(); return removed; } public void RemoveAt(int index) { _sources.RemoveAt(index); _config.ReloadSources(); } //這里省略了實現(xiàn)了實現(xiàn)IList接口的其他操作 //ConfigurationSources本身就是IList<IConfigurationSource> }
正如我們看到的那樣ConfigurationSources本身就是一個IConfigurationSource的集合,在新的.Net體系中微軟喜歡把集合相關(guān)的操作封裝一個Collection類,這樣的好處就是讓大家能更清晰的了解它是功能實現(xiàn)類,而不在用一個數(shù)據(jù)結(jié)構(gòu)的眼光去看待。通過源碼我們還看到了Add方法里還調(diào)用了ConfigurationManager的AddSource方法,這究竟是一個什么操作我們來看下[點擊查看源碼]
private readonly object _providerLock = new(); private readonly List<IConfigurationProvider> _providers = new(); private readonly List<IDisposable> _changeTokenRegistrations = new(); private void AddSource(IConfigurationSource source) { lock (_providerLock) { //在IConfigurationSource中得到IConfigurationProvider實例 var provider = source.Build(this); //添加到_providers集合中 //我們提到過從配置源得到的配置都是通過IConfigurationProvider得到的 _providers.Add(provider); //IConfigurationProvider的Load方法是從配置源中得到配置數(shù)據(jù)加載到程序內(nèi)存中 provider.Load(); //注冊更改令牌操作,使得配置可以進行動態(tài)刷新加載 _changeTokenRegistrations.Add(ChangeToken.OnChange(() => provider.GetReloadToken(), () => RaiseChanged())); } //添加新的配置源要刷新令牌操作 RaiseChanged(); } private ConfigurationReloadToken _changeToken = new(); private void RaiseChanged() { //每次對配置源進行更改操作需要得到新的更改令牌實例,用于可重復(fù)通知配置變更相關(guān) var previousToken = Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken()); previousToken.OnReload(); }
從上面的ConfigurationSources方法里我們可以看到動態(tài)的針對ConfigurationSources里的ConfigurationSource進行更改會每次都調(diào)用ReloadSources
方法,我們來看一下它的實現(xiàn)[點擊查看源碼]
private readonly object _providerLock = new(); private void ReloadSources() { lock (_providerLock) { //釋放原有操作 DisposeRegistrationsAndProvidersUnsynchronized(); //清除更改令牌 _changeTokenRegistrations.Clear(); //清除_providers _providers.Clear(); //重新加載_providers foreach (var source in _sources) { _providers.Add(source.Build(this)); } //重新加載數(shù)據(jù)添加通知令牌 foreach (var p in _providers) { p.Load(); _changeTokenRegistrations.Add(ChangeToken.OnChange(() => p.GetReloadToken(), () => RaiseChanged())); } } RaiseChanged(); }
這個方法幾乎是重新清除了原來的操作,然后完全的重新加載一遍數(shù)據(jù),理論上來說是一個低性能的操作,不建議頻繁使用。還有因為ConfigurationManager實現(xiàn)了IConfigurationBuilder接口所以也必然實現(xiàn)了它的Build方法少不了,看一下它的實現(xiàn)[點擊查看源碼]
IConfigurationRoot IConfigurationBuilder.Build() => this;
這波操作真的很真的很騷氣,我即是IConfigurationRoot我也是IConfigurationBuilder,反正操作都是我自己,所以這里你可勁的Build也不影響啥,反正得到的也都是一個ConfigurationManager實例。到了這里結(jié)合我們之前了解到的傳統(tǒng)的IConfigurationBuilder和IConfiguration關(guān)系,以及我們上面展示的展示的ConfigurationSources類的實現(xiàn)和ConfigurationManager的AddSource方法。其實我們可以發(fā)現(xiàn)我們上面展示的ConfigurationManager類的相關(guān)操作其實就是實現(xiàn)了之前ConfigurationBuilder類里的操作。其實這里微軟可以不用實現(xiàn)ConfigurationSources類完全基于ConfigurationBuilder也能實現(xiàn)一套,但是顯然微軟沒這么做,具體想法咱們不得而知,估計是只想以來抽象,而并不像以來原來的實現(xiàn)方式吧。
我們上面展示的這一部分的ConfigurationManager代碼,其實就是替代了原來的ConfigurationBuilder類的功能。
讀取操作
上面我們看到了在ConfigurationManager中關(guān)于以前ConfigurationManager類的實現(xiàn)。接下來我們看一下讀取相關(guān)的操作,即在這里ConfigurationManager成為了IConfiguration實例,所以我們先來看下IConfiguration接口的定義[點擊查看源碼]
public interface IConfiguration { /// <summary> /// 通過配置名稱獲取值 /// </summary> /// <returns></returns> string this[string key] { get; set; } /// <summary> /// 獲取一個配置節(jié)點 /// </summary> /// <returns></returns> IConfigurationSection GetSection(string key); /// <summary> /// 獲取所有子節(jié)點 /// </summary> /// <returns></returns> IEnumerable<IConfigurationSection> GetChildren(); /// <summary> /// 刷新數(shù)據(jù)通知 /// </summary> /// <returns></returns> IChangeToken GetReloadToken(); }
通過代碼我們看到了IConfiguration的定義,也就是在ConfigurationManager類中必然也實現(xiàn)也這幾個操作,首先便是通過索引器直接根據(jù)配置的名稱獲取值得操作[點擊查看源碼]
private readonly object _providerLock = new(); private readonly List<IConfigurationProvider> _providers = new(); /// <summary> /// 可讀可寫的操作 /// </summary> /// <returns></returns> public string this[string key] { get { lock (_providerLock) { //通過在IConfigurationProvider集合中獲取配置值 return ConfigurationRoot.GetConfiguration(_providers, key); } } set { lock (_providerLock) { //也可以把值放到IConfigurationProvider集合中 ConfigurationRoot.SetConfiguration(_providers, key, value); } } }
其中_providers中的值是我們在AddSource方法中添加進來的,這里的本質(zhì)其實還是針對ConfigurationRoot做了封裝。ConfigurationRoot
實現(xiàn)了IConfigurationRoot
接口,IConfigurationRoot
實現(xiàn)了IConfiguration
接口。而ConfigurationRoot的GetConfiguration方法和SetConfiguration是最直觀體現(xiàn)ConfigurationRoot本質(zhì)就是IConfigurationProvider包裝的證據(jù)。我們來看一下ConfigurationRoot這兩個方法的實現(xiàn)[點擊查看源碼]
internal static string GetConfiguration(IList<IConfigurationProvider> providers, string key) { //倒序遍歷providers,因為Configuration采用的后來者居上的方式,即后注冊的Key會覆蓋先前注冊的Key for (int i = providers.Count - 1; i >= 0; i--) { IConfigurationProvider provider = providers[i]; //如果找到Key的值就直接返回 if (provider.TryGet(key, out string value)) { return value; } } return null; } internal static void SetConfiguration(IList<IConfigurationProvider> providers, string key, string value) { if (providers.Count == 0) { throw new InvalidOperationException(""); } //給每個provider都Set這個鍵值,雖然浪費了一部分內(nèi)存,但是可以最快的獲取 foreach (IConfigurationProvider provider in providers) { provider.Set(key, value); } }
關(guān)于GetSection的方法實現(xiàn),本質(zhì)上是返回ConfigurationSection實例,ConfigurationSection本身也是實現(xiàn)了IConfiguration接口,所有關(guān)于配置獲取的操作出口都是面向IConfiguration的。
public IConfigurationSection GetSection(string key) => new ConfigurationSection(this, key);
GetChildren方法是獲取配置的所有子節(jié)點的操作,本質(zhì)是返回IConfigurationSection的集合,實現(xiàn)方式如如下
private readonly object _providerLock = new(); public IEnumerable<IConfigurationSection> GetChildren() { lock (_providerLock) { //調(diào)用了GetChildrenImplementation方法 return this.GetChildrenImplementation(null).ToList(); } }
這里調(diào)用了GetChildrenImplementation方法,而GetChildrenImplementation是一個擴展方法,我們來看一下它的實現(xiàn)[點擊查看源碼]
internal static IEnumerable<IConfigurationSection> GetChildrenImplementation(this IConfigurationRoot root, string path) { //在當(dāng)前ConfigurationManager實例中獲取到所有的IConfigurationProvider實例 //然后包裝成IConfigurationSection集合 return root.Providers .Aggregate(Enumerable.Empty<string>(), (seed, source) => source.GetChildKeys(seed, path)) .Distinct(StringComparer.OrdinalIgnoreCase) .Select(key => root.GetSection(path == null ? key : ConfigurationPath.Combine(path, key))); }
通過這段代碼再次應(yīng)驗了那句話所有獲取配置數(shù)據(jù)都是面向IConfiguration
接口的,數(shù)據(jù)本質(zhì)都是來自于IConfigurationProvider
讀取配置源中的數(shù)據(jù)。
ConfigurationBuilderProperties
在ConfigurationManager中還包含了一個Properties屬性,這個屬性本質(zhì)來源于IConfigurationBuilder。在IConfigurationBuilder中它和IConfigurationSource是平行關(guān)系,IConfigurationSource用于在配置源中獲取數(shù)據(jù),而Properties是在內(nèi)存中獲取數(shù)據(jù),本質(zhì)是一個字典
private readonly ConfigurationBuilderProperties _properties = new ConfigurationBuilderProperties(this); IDictionary<string, object> IConfigurationBuilder.Properties => _properties;
這里咱們就不細說這個具體實現(xiàn)了,我們知道它本質(zhì)是字典,然后操作都是純內(nèi)存的操作即可,來看一下它的定義[點擊查看源碼]
private class ConfigurationBuilderProperties : IDictionary<string, object> { }
基本上許多緩存機制即內(nèi)存操作都是基于字典做的一部分實現(xiàn),所以大家對這個實現(xiàn)的方式有一定的認識即可,即使在配置體系的核心操作ConfigurationProvider中讀取的配置數(shù)據(jù)也是存放在字典中的。這個可以去ConfigurationProvider類中自行了解一下[點擊查看源碼]
protected IDictionary<string, string> Data { get; set; } protected ConfigurationProvider() { Data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); }
總結(jié)
通過本文我們了解到了.Net6配置體系中的新成員ConfigurationManager
,它是一個新內(nèi)容但不是一個新技術(shù),因為它是在原有的配置體系中封裝了一個新的外觀,以簡化原來對配置相關(guān)的操作。原來對配置的操作需要涉及IConfigurationBuilder和IConfiguration兩個抽象操作,而新的ConfigurationManager只需要一個類,其本質(zhì)是因為ConfigurationManage同時實現(xiàn)了IConfigurationBuilder和IConfiguration接口,擁有了他們兩個體系的能力。整體來說重寫了IConfigurationBuilder的實現(xiàn)為主,而讀取操作主要還是借助原來的ConfigurationRoot對節(jié)點數(shù)據(jù)的讀取操作。
到此這篇關(guān)于.NET6?ConfigurationManager的實現(xiàn)的文章就介紹到這了,更多相關(guān).NET6?ConfigurationManager實現(xiàn)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Asp.net treeview實現(xiàn)無限級樹實現(xiàn)代碼
最近研究了一下treeview,發(fā)現(xiàn)有兩種實現(xiàn)無限級樹的方法,文字不想多寫,直入主題。2009-09-09Web.config 和 App.config 的區(qū)別分析
Web.config 和 App.config 的區(qū)別分析,需要的朋友可以參考一下2013-05-05TrieTree服務(wù)-組件構(gòu)成及其作用介紹
本文將一步步教你配置和使用TrieTree服務(wù),需要的朋友可以參考下2013-01-01ASP.NET技巧:做個DataList可分頁的數(shù)據(jù)源
ASP.NET技巧:做個DataList可分頁的數(shù)據(jù)源...2006-09-09asp.net 在處理向該請求提供服務(wù)所需的配置文件時出錯
遭遇:“說明: 在處理向該請求提供服務(wù)所需的配置文件時出錯。請檢查下面的特定錯誤詳細信息并適當(dāng)?shù)匦薷呐渲梦募!卞e誤2010-03-03