在本系列中,我将探讨.NET6中的一些新功能。关于.NET6的文章很多,包括许多来自.NET和ASP.NET团队本身的文章。在本系列中,我将探索这些功能背后的一些代码。在这第一篇文章中,看一看ConfigurationManager类,解释添加它的原因,并看一下它的一些实现代码。1什么是ConfigurationManager如果你的第一反应是“什么是ConfigurationManager”,别着急,你没有错过一个重要公告:加入ConfigurationManager是为了支持ASP.NETCore新的WebApplication模型,用来简化ASP.NETCore启动代码。然而,ConfigurationManager主要是一个实现细节。引入它是为了优化特定场景(我很快就会谈到),但在大多数情况下,您不需要(也不会)知道您正在使用它。在我们讨论ConfigurationManager本身之前,让我们看一下它替换了什么以及为什么替换。2.NET5/.NET5公开了围绕配置的各种类型,但直接在您的应用程序中使用的两个主要类型是:IConfigurationBuilder-用于添加配置源。在构建器上调用Build()会读取每个配置源并构建最终配置。IConfigurationRoot-表示最终的“构建”配置。IConfigurationBuilder接口主要是配置源列表的包装器。配置提供程序通常包括扩展方法(例如AddJsonFile()和AddAzureKeyVault())以将配置源添加到源列表。publicinterfaceIConfigurationBuilder{IDictionaryProperties{get;}IListSources{get;}IConfigurationBuilderAdd(IConfigurationSourcesource);IConfigurationRootBuild();}同时IConfigurationRoot表示最后“层”的配置值,结合每个配置源提供所有配置值的最终“平面”视图。后面的配置提供者(环境变量)覆盖了前面的配置提供者(appsettings.json、sharedsettings.json)添加的值。在.NET5及更早版本中,IConfigurationBuilder和IConfigurationRoot接口分别由ConfigurationBuilder和ConfigurationRoot实现。如果您直接使用类型,您可能会这样做:varbuilder=newConfigurationBuilder();//addstaticvaluesbuilder.AddInMemoryCollection(newDictionary{{"MyKey","MyValue"},});//addvaluesfromajsonfilebuilder.AddJsonFile("appsettings.json");//createtheIConfigurationRootinstanceIConfigurationRootconfig=builder.Build();stringvalue=config["MyKey"];//getavalueIConfigurationSectionsection=config.GetSection("SubSection");//getasectioninatypicalASP.NET在Core应用程序中,您不会自己创建ConfigurationBuilder或调用Build(),但除此之外,这是在幕后发生的事情。这两种类型之间有明显的区别,并且在大多数情况下配置系统运行良好,那么为什么我们需要.NET6中的新类型?3.NET5中“部分构建”配置的问题这种设计的主要问题是当您需要“部分”构建配置时。当您将配置存储在AzureKeyVault等服务甚至数据库中时,这是一个常见问题。例如,这是在ASP.NETCore中从ConfigureAppConfiguration()中的AzureKeyVault读取机密的建议方法:.ConfigureAppConfiguration((context,config)=>{//"normal"configurationetcconfig.AddJsonFile("appsettings.json");config.AddEnvironmentVariables();if(context.HostingEnvironment.IsProduction()){IConfigurationRootpartialConfig=config.Build();//buildpartialconfigstringkeyVaultName=partialConfig["KeyVaultName"];//从配置读取值varsecretClient=newSecretClient(newUri($"https://{keyVaultName}.vault.azure.net/"),newDefaultAzureCredential());config.AddAzureKeyVault(secretClient,newKeyVaultSecretManager());//addanextraconfigurationsource//Theframeworkcallsconfig.Build()AGAINtobuildthefinalIConfigurationRoot}})配置AzureKeyVault提供者需要一个配置值,因此您陷入了先有鸡还是先有蛋的问题——在建立配置之前无法添加配置源。解决方案是:添加“初始”配置值;通过调用IConfigurationBuilder.Build()构建“部分”配置结果;从生成的IConfigurationRoot中检索所需的配置值;使用这些值来添加剩余的配置源;框架隐式调用IConfigurationBuilder.Build()以生成最终的IConfigurationRoot并将其用于最终的应用程序配置。这整个过程有点乱,但是本身没什么问题,那有什么不好的地方呢?缺点是我们必须调用Build()两次:一次仅使用第一个源构建IConfigurationRoot,一次使用构建IConfiguartionRoot的所有源,包括AzureKeyVault源。在默认的ConfigurationBuilder实现中,调用Build()遍历所有源、加载提供程序并将它们传递给ConfigurationRoot的新实例。publicIConfigurationRootBuild(){varproviders=newList();foreach(IConfigurationSourcesourceinSources){IConfigurationProviderprovider=source.Build(this);providers.Add(provider);}returnnewConfigurationRoot(providers);}然后ConfigurationRoot依次循环遍历这些provider,并加载配置值。publicclassConfigurationRoot:IConfigurationRoot,IDisposable{privatereadonlyIList_providers;privatereadonlyIList_changeTokenRegistrations;publicConfigurationRoot(IListproviders){_providers;_changeTokenRegistrations=newList(providers.Count);foreach(IConfigurationProviderpinproviders){pLoad();_changeTokenRegistrations.Add(ChangeToken.OnChange(()=>p.GetReloadToken(),()=>RaiseChanged()));}}//...remainderofimplementation}如果调用Build()两次,则所有这一切都发生了两次。一般来说,多次从配置源获取数据没有问题,但这是不必要的工作,通常涉及(相对较慢的)文件读取等。这是一种常见的模式,因此在.NET6中引入了一种新类型来避免这种情况“重构”,ConfigurationManager。4..NET6中的配置管理器作为.NET6中“简化”应用程序模型的一部分,.NET团队添加了一个新的配置类型——ConfigurationManager。此类型同时实现IConfigurationBuilder和IConfigurationRoot。通过将这两种实现组合到一个类型中,.NET6可以优化上一节中显示的通用模式。使用ConfigurationManager,当添加IConfigurationSource时(例如当您调用AddJsonFile()时),会立即加载提供程序并更新配置。这避免了在部分构建的情况下必须多次加载配置源。实现这一点比听起来要难一些,因为IConfigurationBuilder接口将源公开为IList:publicinterfaceIConfigurationBuilder{IListSources{get;}//..othermembers}公开了Add()和Remove()函数。如果使用简单的List<>,消费者可以在ConfigurationManager不知情的情况下添加和删除配置提供者。为解决此问题,ConfigurationManager使用自定义IList<>实现。这包含对ConfigurationManager实例的引用,因此任何更改都可以反映在配置中:{_sources.Add(source);_config.AddSource(source);//将源添加到ConfigurationManager}publicboolRemove(IConfigurationSourcesource){varremoved=_sources.Remove(source);_config.ReloadSources();//resetsourcesintheConfigurationManagerreturnremoved;}//...additionalimplementation}通过使用自定义IList<>实现,ConfigurationManager确保每当添加新源时调用AddSource()。这是ConfigurationManager的优势:调用AddSource()立即加载源:;_changeTokenRegistrations.Add(ChangeToken.OnChange(()=>provider.GetReloadToken(),()=>RaiseChanged()));}RaiseChanged();}}此方法立即调用IConfigurationSource上的Build以创建IConfigurationProvider,并且它添加到提供商列表中。接下来,该方法调用IConfigurationProvider.Load()。这会将数据加载到提供程序中(例如,从环境变量、JSON文件或AzureKeyVault),这是“昂贵”的步骤,而这就是加载数据的全部内容。在“正常”情况下,您只需添加IConfigurationBuilder添加源代码,并且可能需要多次构建它,这提供了“最佳”方法-源代码加载一次,并且仅加载一次。ConfigurationManager中Build()的实现现在只返回自身:IConfigurationRootIConfigurationBuilder.Build()=>this;当然,软件开发就是权衡取舍。如果您只添加源代码,则在添加源代码时逐步构建源代码效果很好。但是,如果您调用任何其他IList<>函数,例如Clear()、Remove()或索引器,ConfigurationManager必须调用ReloadSources():);_providers.Clear();foreach(varsourcein_sources){_providers.Add(source.Build(this));}foreach(varpin_providers){p.Load();_changeTokenRegistrations.Add(ChangeToken.OnChange(()=>p.GetReloadToken(),()=>RaiseChanged()));}}RaiseChanged();}如您所见,如果任何一个源更改,ConfigurationManager必须删除所有内容并重新开始,遍历每个源,重新加载它们.如果您对配置源进行大量操作,这很快就会变得昂贵,并且会完全否定ConfigurationManager的原始优势。当然,删除源的情况很少见,因此ConfigurationManager针对最常见的情况进行了优化。谁会猜到?下表给出了使用ConfigurationBuilder和ConfigurationManager进行各种操作的相对成本的最终总结:5.你关心ConfigurationManager吗?那么看了这么多,你是不是应该在乎自己用的是ConfigurationManager还是ConfigurationBuilder呢?也许不应该。.NET6中引入的新WebApplicationBuilder使用ConfigurationManager,它优化了我在上面描述的需要“部分构建”配置的用例。但是,在ASP.NETCore早期版本中引入的WebHostBuilder或HostBuilder在.NET6中仍然得到很好的支持,它们在幕后继续使用ConfigurationBuilder和ConfigurationRoot类型。我认为您需要小心的唯一情况是,如果您在某处依赖IConfigurationBuilder或IConfigurationRoot作为ConfigurationBuilder或ConfigurationRoot的具体类型。这似乎不太可能发生在我身上,如果你依赖这个,我很想知道为什么。但是除了这个小众例外,“旧”类型并没有消失,所以没有必要担心。如果您需要进行“部分构建”并且您使用了新的WebApplicationBuilder,您应该为您的应用程序的性能更高而感到高兴。6.总结在本文中,我描述了.NET6中引入的新的ConfigurationManager类型,并在MinimalAPI示例中被新的WebApplicationBuilder使用。ConfigurationManager的引入是为了优化您需要“部分构建”配置的常见情况。这通常是因为配置提供程序本身需要一些配置,例如,从AzureKeyVault加载机密需要配置指示要使用哪个Vault存储库。ConfigurationManager优化了这种情况:它在添加源后立即加载它们,而不是等到您调用Build()。这避免了在“部分构建”情况下“重建”配置的需要,而代价是可能变得更昂贵的其他不常见操作(例如删除源)。