Dotnet Core 技術(shù)之Dotnet 6.0 深度探索
本文轉(zhuǎn)載自微信公眾號(hào)「老王Plus」,作者老王Plus的老王。轉(zhuǎn)載本文請(qǐng)聯(lián)系老王Plus公眾號(hào)。
Dotnet 6.0 大家都裝了沒?
我打算開個(gè)專題,系統(tǒng)地寫一寫 Dotnet 6.0 在各個(gè)方面的特性,以及全新的開發(fā)方式。也是因?yàn)樽罱懻?6.0 比較多,看到很多人的畏難情緒,所以打算寫寫相關(guān)的內(nèi)容。
了解了,就不怕了。
要寫的內(nèi)容很多,我會(huì)分幾篇來寫。
今天是第一篇:ConfigurationManager,配置管理器。
ConfigurationManager 是干什么用的?
引用微軟官方的說法:ConfigurationManager 是用來支持 ASP.Net Core 的新的 WebApplication 模型。這個(gè)模型主要的作用是在一些特定的場(chǎng)景下(后面我們會(huì)說到),用來簡(jiǎn)化 ASP.NET Core 的啟動(dòng)代碼。
當(dāng)然,如果我們?nèi)タ?MSDN 的文檔,會(huì)發(fā)現(xiàn) ConfigurationManager 本身實(shí)現(xiàn)還是挺復(fù)雜的。好在,大多數(shù)情況下,這是一個(gè)半隱藏的東西,你可能都意識(shí)不到你已經(jīng)用到了它。
那它到底是干什么用的?
這得從 .Net 5.0 的 Configuration 說起。
.Net 5.0 里的 Configuration
Configuration 配置,從 3.1 到 5.0,增加了很多很多的配置類型,如果你去 MSDN 上看,有好幾大篇。
這里面,我們接觸最多的是兩個(gè):
- IConfigurationBuilder - 這個(gè)接口主要用來增加配置源,并在構(gòu)建器上調(diào)用 Build() 來讀取每個(gè)配置源,并形成最終的配置
- IConfigurationRoot - 這就是上面 Build() 完成后形成的配置,我們會(huì)從這里面讀配置值
在實(shí)際應(yīng)用中,IConfigurationBuilder 通常被我們用做配置源列表的包裝器,最常用的是通過 AddJsonFile(),將配置源添加到源列表中。看到 AddJsonFile(),你是不是想到了什么?
簡(jiǎn)單來說,IConfigurationBuilder 是這樣的:
- public interface IConfigurationBuilder
- {
- IDictionary<string, object> Properties { get; }
- IList<IConfigurationSource> Sources { get; }
- IConfigurationBuilder Add(IConfigurationSource source);
- IConfigurationRoot Build();
- }
而 IConfigurationRoot,里面放的是經(jīng)過合并的配置值。這個(gè)合并需要注意一下,多個(gè)配置源逐個(gè)加入時(shí),相同名稱的項(xiàng),后面的配置會(huì)覆蓋前面的項(xiàng)。
在 .Net 5.0 以前,IConfigurationBuilder 和 IConfigurationRoot 接口分別由 ConfigurationBuilder 和 ConfigurationRoot 實(shí)現(xiàn)。使用時(shí)通常是這么寫:
- var builder = new ConfigurationBuilder();
- // 加入靜態(tài)值
- builder.AddInMemoryCollection(new Dictionary<string, string>
- {
- { "MyKey", "MyValue" },
- });
- // 加入文件
- builder.AddJsonFile("appsettings.json");
- IConfigurationRoot config = builder.Build();
- string value = config["MyKey"]; // 取一個(gè)值
- IConfigurationSection section = config.GetSection("SubSection"); // 取一個(gè)節(jié)
這是在 Console 程序中。
在 ASP.NET Core 中,通常不需要這么顯式的 new 和 Build(),但事實(shí)上也是調(diào)用的這個(gè)接口。
在默認(rèn)的 ConfigurationBuilder 實(shí)現(xiàn)中,調(diào)用 Build() 將遍歷所有的源,加載 Provider 程序,并將它們傳遞給一個(gè)新的ConfigurationRoot 實(shí)例:
- public IConfigurationRoot Build()
- {
- var providers = new List<IConfigurationProvider>();
- foreach (IConfigurationSource source in Sources)
- {
- IConfigurationProvider provider = source.Build(this);
- providers.Add(provider);
- }
- return new ConfigurationRoot(providers);
- }
然后,ConfigurationRoot 依次遍歷每個(gè)提供程序并加載配置值:
- public class ConfigurationRoot : IConfigurationRoot, IDisposable
- {
- private readonly IList<IConfigurationProvider> _providers;
- private readonly IList<IDisposable> _changeTokenRegistrations;
- public ConfigurationRoot(IList<IConfigurationProvider> providers)
- {
- _providers = providers;
- _changeTokenRegistrations = new List<IDisposable>(providers.Count);
- foreach (IConfigurationProvider p in providers)
- {
- p.Load();
- _changeTokenRegistrations.Add(ChangeToken.OnChange(() => p.GetReloadToken(), () => RaiseChanged()));
- }
- }
- // ...
- }
這種架構(gòu),會(huì)有個(gè)小問題。在團(tuán)隊(duì)開發(fā)的時(shí)候,在沒有統(tǒng)一溝通的情況下,有可能會(huì)在多處調(diào)用 Build()。當(dāng)然這也沒什么問題。只不過,正常來說這個(gè)沒有必要,畢竟這是在讀文件,會(huì)很慢。
不過,在 .Net 5.0 之前,都是這么做。
可喜的是,在 .Net 6.0 里,微軟也注意到這個(gè)問題,并引入了一個(gè)新的類型:ConfigurationManager。
.Net 6.0 里的 ConfigurationManager
ConfigurationManager 是一個(gè) .Net 6.0 中新的配置類型。這個(gè)類型也同樣實(shí)現(xiàn)了兩個(gè)接口:IConfigurationBuilder 和 IConfigurationRoot。那么,通過這兩個(gè)接口的實(shí)現(xiàn),我們可以簡(jiǎn)化上一節(jié)講到的 .Net 5.0 中的通用模式。
不過,還是有一點(diǎn)點(diǎn)區(qū)別。這里 IConfigurationBuilder 將源保存為 IList:
- public interface IConfigurationBuilder
- {
- IList<IConfigurationSource> Sources { get; }
- // ...
- }
這樣做有一個(gè)好處,就是對(duì)于源 IList,就有了 Add() 和 Remove() 方法,我們可以在不知道 ConfigurationManager 的情況下增加和刪除配置提供程序。
- private class ConfigurationSources : IList<IConfigurationSource>
- {
- private readonly List<IConfigurationSource> _sources = new();
- private readonly ConfigurationManager _config;
- public ConfigurationSources(ConfigurationManager config)
- {
- _config = config;
- }
- public void Add(IConfigurationSource source)
- {
- _sources.Add(source);
- _config.AddSource(source); // 增加源
- }
- public bool Remove(IConfigurationSource source)
- {
- var removed = _sources.Remove(source); // 刪除源
- _config.ReloadSources(); // 重新加載源
- return removed;
- }
- // ...
- }
這樣做可以確保 ConfigurationManager 在改變?cè)吹?IList 時(shí),能自動(dòng)加載源的配置數(shù)據(jù)。
看一下 ConfigurationManager.AddSource 的定義:
- public class ConfigurationManager
- {
- private void AddSource(IConfigurationSource source)
- {
- lock (_providerLock)
- {
- IConfigurationProvider provider = source.Build(this);
- _providers.Add(provider);
- provider.Load();
- _changeTokenRegistrations.Add(ChangeToken.OnChange(() => provider.GetReloadToken(), () => RaiseChanged()));
- }
- RaiseChanged();
- }
- }
這個(gè)方法會(huì)立即調(diào)用 IConfigurationSource 的 Build() 方法來創(chuàng)建 IConfigurationProvider,并加入到源列表中。
下面,這個(gè)方法就調(diào)用 IConfigurationProvider 的 Load() 方法,將數(shù)據(jù)加載到 Provider。
這個(gè)方法解決了一件事,就是當(dāng)我們需要從不同的位置向 IConfigurationBuilder 加入各種源時(shí),源只需要加載一次,而且只會(huì)加載一次。
上面的代碼是增加源。當(dāng)我們需要 Remove() 源,或者干脆清除掉全部的源 Clear() 時(shí),就需要調(diào)用 ReloadSource():
- private void ReloadSources()
- {
- lock (_providerLock)
- {
- DisposeRegistrationsAndProvidersUnsynchronized();
- _changeTokenRegistrations.Clear();
- _providers.Clear();
- foreach (var source in _sources)
- {
- _providers.Add(source.Build(this));
- }
- foreach (var p in _providers)
- {
- p.Load();
- _changeTokenRegistrations.Add(ChangeToken.OnChange(() => p.GetReloadToken(), () => RaiseChanged()));
- }
- }
- RaiseChanged();
- }
當(dāng)然,看懂上面的代碼,也就明白兩件事:
- 增加源是代碼最小的,加到列表中就行了 ;
- 刪除或更改源代碼會(huì)有點(diǎn)大,需要重新遍歷加載所有源。
如果需要對(duì)配置的源進(jìn)行大量的操作,這樣的代價(jià)會(huì)比較大。不過,這種情況會(huì)很不常見。
總結(jié)一下
.Net 6.0 引入了一個(gè)新的 ConfigurationManager,用來優(yōu)化配置的構(gòu)建。
ConfigurationManager 同樣實(shí)現(xiàn)了 ConfigurationBuilder 和 ConfigurationRoot。這算是個(gè)兼容性的設(shè)置,主要是為了支持 WebHostBuilder 和 HostBuilder 中對(duì)配置的調(diào)用。同時(shí),也兼容了早期代碼中的調(diào)用方式。所以,代碼升級(jí)時(shí),相關(guān)配置調(diào)用的部分,如果不想改代碼,是完全可以的。而如果想做點(diǎn)改動(dòng),就換成使用 ConfigurationManager,或者通過 WebApplicationBuilder 來加載(會(huì)自動(dòng)調(diào)用 ConfigurationManager),應(yīng)用程序會(huì)有更好的性能。
這算是一個(gè)小禮物,相信也是微軟權(quán)衡以后的結(jié)果。