[探索 .NET 6]01 揭开 ConfigurationManager 的面纱

在这个系列中,我将探索一下 .NET 6 中的一些新特性。已经有很多关于 .NET 6 的内容,包括很多来自 .NET 和 ASP.NET 团队本身的文章。在这个系列中,我将探索一下这些特性背后的一些代码。

在这第一篇文章中,来研究一下 ConfigurationManager 类,讲一下为什么要新增这个类,并看一下它的的一些实现代码。

1什么是 ConfigurationManager

如果你的第一反应是“什么是 ConfigurationManager”,那么不用担心,你没有错过一个重要的公告:

加入 ConfigurationManager 是为了支持 ASP.NET Core 的新 WebApplication 模型,用于简化 ASP.NET Core 的启动代码。然而 ConfigurationManager 在很大程度上是一个实现细节。它的引入是为了优化一个特定的场景(我很快会讲),但在大多数情况下,你不需要(也不会)知道你在使用它。

在我们讨论 ConfigurationManager 本身之前,我们先来看看它所取代的东西和原因。

2.NET 5 中的配置

.NET 5 围绕配置暴露了多种类型,但在你的应用程序中直接使用的两个主要类型是:

  • IConfigurationBuilder - 用来添加配置源。在构建器上调用 Build() 读取每个配置源,并构建最终的配置。

  • IConfigurationRoot - 代表最终“构建”好的配置。

IConfigurationBuilder 接口主要是一个围绕配置源列表的封装器。配置提供者通常包括扩展方法(如 AddJsonFile()AddAzureKeyVault()),将配置源添加到 Sources 列表中。

public interface IConfigurationBuilder
{IDictionary<string, object> Properties { get; }IList<IConfigurationSource> Sources { get; }IConfigurationBuilder Add(IConfigurationSource source);IConfigurationRoot Build();
}

同时,IConfigurationRoot 代表最终“层”的配置值,结合了每个配置源的所有值,以提供所有配置值的最终“平面”视图。

54392244af52eba4a9ba8049e115e1c6.png

后者配置提供者(环境变量)覆盖了前者配置提供者(appsettings.jsonsharedsettings.json)添加的值。

在 .NET 5 及以前的版本中,IConfigurationBuilderIConfigurationRoot 接口分别由 ConfigurationBuilderConfigurationRoot 实现。如果你直接使用这些类型,你可能会这样做:

var builder = new ConfigurationBuilder();// add static values
builder.AddInMemoryCollection(new Dictionary<string, string>
{{ "MyKey", "MyValue" },
});// add values from a json file
builder.AddJsonFile("appsettings.json");// create the IConfigurationRoot instance
IConfigurationRoot config = builder.Build();string value = config["MyKey"]; // get a value
IConfigurationSection section = config.GetSection("SubSection"); //get a section

在一个典型的 ASP.NET Core 应用程序中,你不会自己创建 ConfigurationBuilder,或调用 Build(),但除此之外,这就是幕后发生的事情。这两种类型之间有明确的分离,而且在大多数情况下,配置系统运行良好,那么为什么我们在.NET 6 中需要一个新类型呢?

3.NET 5 中“部分构建”配置的问题

这种设计的主要问题是在你需要“部分”构建配置的时候。当你将配置存储在 Azure Key Vault 等服务中,甚至是数据库中时,这是一个常见的问题。

例如,以下是在 ASP.NET Core 中的 ConfigureAppConfiguration() 里面从 Azure Key Vault 读取 secrects 的建议方式:

.ConfigureAppConfiguration((context, config) =>
{// "normal" configuration etcconfig.AddJsonFile("appsettings.json");config.AddEnvironmentVariables();if (context.HostingEnvironment.IsProduction()){IConfigurationRoot partialConfig = config.Build(); // build partial configstring keyVaultName = partialConfig["KeyVaultName"]; // read value from configurationvar secretClient = new SecretClient(new Uri($"https://{keyVaultName}.vault.azure.net/"),new DefaultAzureCredential());config.AddAzureKeyVault(secretClient, new KeyVaultSecretManager()); // add an extra configuration source// The framework calls config.Build() AGAIN to build the final IConfigurationRoot}
})

配置 Azure Key Vault 提供者需要一个配置值,所以你陷入了一个鸡和蛋的问题--在你建立配置之前,你无法添加配置源。

解决办法是:

  • 添加“初始”配置值;

  • 通过调用 IConfigurationBuilder.Build() 构建“部分”配置结果;

  • 从生成的 IConfigurationRoot 中检索所需的配置值;

  • 使用这些值来添加剩余的配置源;

  • 框架隐含地调用 IConfigurationBuilder.Build(),生成最终的 IConfigurationRoot 并将其用于最终的应用配置。

这整个过程有点乱,但它本身并没有什么问题,那么缺点是什么呢?

缺点是我们必须调用 Build() 两次:一次是只使用第一个源来构建 IConfigurationRoot,另一次是使用所有源来构建 IConfiguartionRoot,包括 Azure Key Vault 源。

在默认的 ConfigurationBuilder 实现中,调用 Build() 会遍历所有的源,加载提供者,并将这些传递给 ConfigurationRoot 的一个新实例。

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 依次循环遍历这些提供者,并加载配置值。

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()));}}// ... remainder of implementation
}

如果你在应用启动时调用 Build() 两次,那么所有这些都会发生两次。

一般来说,从配置源获取数据一次以上并无大碍,但这是不必要的工作,而且经常涉及到(相对缓慢的)文件读取等。

这是一种常见的模式,所以在 .NET 6 中引入了一个新的类型来避免这种“重新构建”,即 ConfigurationManager

4.NET 6 中的配置管理器

作为 .NET 6 中“简化”应用模型的一部分,.NET 团队增加了一个新的配置类型--ConfigurationManager。这种类型同时实现了 IConfigurationBuilderIConfigurationRoot。通过将这两种实现结合在一个单一的类型中,.NET 6 可以优化上一节中展示的常见模式。

有了 ConfigurationManager,当 IConfigurationSource 被添加时(例如当你调用 AddJsonFile() 时),提供者被立即加载,配置被更新。这可以避免在部分构建的情况下不得不多次加载配置源。

由于 IConfigurationBuilder 接口将源作为 IList<IConfigurationSource> 公开,因此实现这一点比听起来要难一些:

public interface IConfigurationBuilder
{IList<IConfigurationSource> Sources { get; }// .. other members
}

ConfigurationManager 的角度来看,这个问题是 IList<> 暴露了 Add()Remove() 函数。如果使用一个简单的 List<>,消费者可以在 ConfigurationManager 不知道的情况下添加和删除配置提供者。

为了解决这个问题,ConfigurationManager 使用一个自定义的 IList<> 实现。这包含对 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); // add the source to the ConfigurationManager}public bool Remove(IConfigurationSource source){var removed = _sources.Remove(source);_config.ReloadSources(); // reset sources in the ConfigurationManagerreturn removed;}// ... additional implementation
}

通过使用一个自定义的 IList<> 实现,ConfigurationManager 确保每当有新的源被添加时就调用 AddSource()。这就是 ConfigurationManager 的优势所在:调用 AddSource() 可以立即加载源:

ublic 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();}
}

这个方法立即在 IConfigurationSource 上调用 Build 来创建 IConfigurationProvider,并将其添加到提供者列表中。

接下来,该方法调用 IConfigurationProvider.Load()。这将把数据加载到提供者中,(例如从环境变量、JSON 文件或 Azure Key Vault),这是“昂贵”的步骤,而这一切就是为了加载数据 在“正常”情况下,你只需向 IConfigurationBuilder 添加源,并可能需要多次构建它,这就给出了“最佳”方法——源被加载一次,且只有一次。

ConfigurationManagerBuild() 的实现现在什么都没做,只是返回它自己:

IConfigurationRoot IConfigurationBuilder.Build() => this;

当然,软件开发是所有关于权衡的问题。如果你只添加源,那么在添加源的时候递增构建源就很有效。然而,如果你调用任何其他 IList<> 函数,如 Clear()Remove() 或索引器,ConfigurationManager 就必须调用 ReloadSources()

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();
}

正如你所看到的,如果任何一个源改变了,ConfigurationManager 必须删除所有的东西并重新开始,迭代每个源,重新加载它们。如果你要对配置源进行大量的操作,这很快就会变得很昂贵,而且会完全否定 ConfigurationManager 的原始优势。

当然,删除源是非常罕见的,所以 ConfigurationManager 是为最常见的情况而优化的。谁能猜到呢?

下表给出了使用 ConfigurationBuilderConfigurationManager 的各种操作的相对成本的最终总结:

28921cd68ccce6c7cdf5439410add179.png

5是否需关心 ConfigurationManager

那么读了这么多,你是否应该关心你是使用 ConfigurationManager 还是 ConfigurationBuilder

也许不应该。

在 .NET 6 中引入的新的 WebApplicationBuilder 使用 ConfigurationManager,它优化了我上面描述的使用情况,即你需要“部分构建”你的配置。

然而,ASP.NET Core 早期版本中引入的 WebHostBuilderHostBuilder 在 .NET 6 中仍然非常受支持,它们继续在幕后使用 ConfigurationBuilderConfigurationRoot 类型。

我认为唯一需要注意的情况是,如果你在某个地方依赖 IConfigurationBuilderIConfigurationRoot 作为具体类型的 ConfigurationBuilderConfigurationRoot。这在我看来是非常不太可能发生的,如果你依赖这一点,我很想知道原因。

但除了这个小众的例外,“老”类型不会消失,所以没有必要担心。如果你需要进行“部分构建”,并且你使用了新的 WebApplicationBuilder,那么你的应用程序将会有更高的性能,这一点你应该感到高兴。

6总结

在这篇文章中,我描述了在 .NET 6 中引入的新的 ConfigurationManager 类型,并在最小(Minimal) API 示例中被新的 WebApplicationBuilder 所使用。引入 ConfigurationManager 是为了优化一种常见的情况,即你需要“部分构建”配置。这通常是因为配置提供者本身需要一些配置,例如,从 Azure Key Vault 加载 secrects,需要配置表明要使用哪个 Vault 库。

ConfigurationManager 优化了这种情况:它在添加源时立即加载,而不是等到你调用 Build()。这就避免了在“部分构建”情况下“重建”配置的需要,其代价是其他不常见操作(如删除一个源)可能变得更昂贵的。

原文:bit.ly/3227vka
作者:Andrew Lock
翻译:精致码农

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/292329.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mysql锁表_MySQL中Alter table 你不知道的性能问题

前言&#xff1a;MySQL 的大表运维总是令人头疼的一件事&#xff0c;特别是大表表结构的修改尤为困难。首先&#xff0c;alter table 的process不可被kill &#xff0c; 一旦执行就不可回退。其次&#xff0c;大多数的alter table操作都会涉及 lock --- copy to new table --- …

打印文件前,千万记得把弹窗叉掉!!!

1 父母能有多迷信&#xff1f;&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼2 打印前千万记得把弹窗关掉&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼3 挺好的&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼4 饭桌上&#xff0c;领导叫你去…

Java队列集合的性能测试

同时开10个线程存入和取出100万的数据&#xff0c;结论如下&#xff1a; DoubleBufferedQueue < ConcurrentLinkedQueue < ArrayBlockingQueue < LinkedBlockingQueue 执行结果如下&#xff1a; 100万 DoubleBufferedQueue入队时间&#xff1a;9510 出队时间&#xff…

linux之man命令用法入门

man命令的解释: man的全称是manual,手册的意思,所以man命令有帮助解释其它命令的作用 基本用法: man + 命令 比如: man pwd 就会出现下面信息 PWD(1) User Commands PWD(1) //注意括号中的数字 NAME //关于命令的名次和说明pwd - print name of current/work…

Node webkit启动最大化窗口

<!DOCTYPE html> <html> <head> </head> <body> <p style"text-align:center;margin-top:10%;color:green;font-weight:bold;font-size:12px">正在加载中...</p> <script language"javascript" …

高效的动态URL限流实现

访问限流对于一个网关来说是个比较重要的功能&#xff0c;它可以根据不同服务的处理能力来控制相关的访问量&#xff0c;从而保障服务更可靠地运行。但是URL控制的路径比较多还加上可动态添加删除&#xff0c;大量的访问匹配会很容易引起性能上的问题&#xff0c;接下来讲解一下…

cocos2d-x学习 之一

最近准备学习cocos2d-x的开发&#xff0c;首先要搭建一下开发环境。今天就先搭建一下开发环境。本人系统为Mint-15 64位的linux&#xff0c;以下的开发环境只用于linux。首先到cocos2d-x的官网上下载安装包&#xff0c;由于cocos2d-x是开源的&#xff0c;所以我们可以查看源码&…

python里while的用法_Python学习笔记之While循环用法分析

本文实例讲述了Python学习笔记之While循环用法。分享给大家供大家参考&#xff0c;具体如下&#xff1a;前面一篇《Python学习笔记之For循环用法》详细介绍了Python for循环&#xff0c;这里再来讲述一下while循环的使用方法&#xff1a;Python 中的While循环For 循环是一种有限…

linux环境下最简单的C语言例子

1、装好linux系统和配置GCC环境 给自己的电脑上安装linux系统,比如我用大的是ubuntu,然后在ubuntu上配置GCC环境,如果不知道配置,请百度“linux上怎么配置GCC环境” 2、打开终端创建文件写代码 用组合命令打开终端 Ctrl+Alt+T 用vim创建文件编写代码 vim hello.c再点击下…

在 App 扩展和主 App 间共享数据

tags: iOS 8,Swift,App Groups 随着 iOS 8 的发布&#xff0c;苹果为广大开发者很多新的 API&#xff0c;其中最突出显著的就非 App Extension 莫属了。这为开发者们又带来了很多机会。 而我们在开发 App Extension 的时候&#xff0c;基本大多数人都会遇到这样一个问题。就是由…

Java8 (1)

参考资料&#xff1a; 《Java8 in Action》 Raoul-Gabriel Urma 一、jdk8 客观的说&#xff0c;Java8是一次有重大演进的版本&#xff0c;甚至很多人认为java8所做的改变&#xff0c;在许多方面都比Java历史上任何一次改变都深远。 Scala&#xff0c;python这样优秀编程语言中对…

[探索 .NET 6]02 比较 WebApplicationBuilder 和 Host

这是『探索 .NET 6』系列的第二篇文章&#xff1a;01 揭开 ConfigurationManager 的面纱02 比较 WebApplicationBuilder 和 Host在 .NET 中&#xff0c;有一种新的“默认”方法用来构建应用程序&#xff0c;即使用 WebApplication.CreateBuilder()。在这篇文章中&#xff0c;我…

python计算公式分母有0_你知道Python中的浮点除法和积分除法吗,python,float,整除,都...

从python2.2开始&#xff0c;便有两种除法运算符&#xff1a;"/"、"//"。两者最大区别在&#xff1a;python2.2前的版本和python2.2以后3.0以前的版本的默认情况下&#xff0c;"/"所做的除法是以一种两个数或者多个数出现一个浮点数结果就以浮点…

计算机科学概论(2)数据的操控和程序的执行

1.CPU是什么&#xff1f;它有什么作用&#xff1f;CPU(Central Processing Unit&#xff0c;中央处理器)负责操控数据在不同位置间的移动及对数据进行处理。它是计算机的核心部件。它主要由三个部分组成&#xff1a;算数/逻辑单元、控制单元、寄存器单元。算数逻辑单元负责在数…

都怪爱因斯坦没说清楚!竟有人相信一个粉笔头就能让全人类多喝100年的热水?...

全世界只有3.14 % 的人关注了爆炸吧知识一个粉笔头一共能释放多少能量爱因斯坦大家肯定都熟悉&#xff0c;相信也有很多朋友听说过质能方程。根据质能方程的公式&#xff0c;我们发现&#xff1a;似乎能量和质量是可以相互转化的。尤其是一些没有系统学习过相对论&#xff0c;又…

linux环境下用TcpDump抓包分析总结

1、手机IP 怎么知道手机ip,输入下面命令 adb shellifconfig 比如得到手机ip 2.0.0.1 2、目标IP 比如目标地址ip为10.0.0.1 3、抓包命令 我们不带端口命令如下 tcpdump -i any host 2.0.0.1 -nv 代码端口的命令如下(端口为50129) tcpdump -i any host 2.0.0.1 and port…

UVa 12100 - Printer Queue

刚A完图书系统那道题&#xff0c;然后看提交次数那个字典的比这道题多&#xff0c;看了看那道更新字典没有思路&#xff0c;就看了这道题&#xff0c;感觉这道题比更新字典简单多了。 #include<iostream> #include<queue> #include<map> using namespace std…

从微信云托管容器镜像的选择-alpine 说起

微信云托管 使用目前主流的容器平台Docker以及容器编排技术Kubernetes&#xff08;简称K8S&#xff09;&#xff0c;来管理你的项目。使用微信云托管需要掌握对Docker的使用&#xff0c;但你无需掌握K8S的使用方法。微信云托管将K8S的运维配置完全接手&#xff0c;你不需要关心…

H5移动开发AUI框架入门---博客园老牛大讲堂

大家都知道H5可以开发移动端的页面&#xff0c;网上提供的移动端的开发都有很多。因为我学习了AUI框架&#xff0c;所以我这里介绍一下移动端AUI框架。--博客园老牛大讲堂 一、AUI框架是什么&#xff1f;---博客园老牛大讲堂 AUI框架就是利用原生的js和css封装成的一些界面。当…