在 .NET 中运行时合并自定义配置部分

Merging custom configuration sections at runtime in .NET

我的问题是关于使用标准 .NET 配置对象和自定义配置元素(可以通过扩展 System.Configuration.ConfigurationSection class). We usually obtain these from the System.Configuration.ConfigurationManager class's methods, GetSection(...) 作为示例来定义。

加载的配置对象似乎是一个 merged 配置对象,其中包含应用程序配置文件中存在的设置(app.configweb.config 开发人员可能创建的文件)以及 machine.config 文件中定义的内容(后者随 .NET Framework 安装一起提供)。

因此,我们可以假设配置首先以 machine.config 分层方式加载,然后任何用户定义的配置覆盖该默认设置,并且可以被这样看待:

我的目标是创建多层配置,以便在 machine.configapp.config 文件:


更新

重点是 - 如果我在 custom.configapp.config[=86= 中定义了配置部分],我需要在调用 ConfigurationManager.GetSection("MyCustomSection") 时获得两个配置的合并版本。理想情况下,如果我能够按照 this MSDN article.

中的描述执行合并,那就太好了

通常,我会编写自己的配置管理器 class 并会尽我所能获得所需的结果,但假设 .NET 框架可以为 machine.configapp.config,我想我可能会受益于框架的内置功能。此外,如果我确实应该求助于我自己的配置管理器实现,我不知道如何手动触发这种合并。

那么,是否可以利用内置配置机制 section/element 与自定义配置文件合并?我对为自定义配置部分开发和支持它特别感兴趣。 System.Configuration 命名空间包含用于构建配置部分和元素的基础对象,这些允许一些与合并相关的设置(例如设置适当的 ConfigurationElementCollectionType)。这些是否仅与 machine.config 合并(或 Web 应用程序中的多层 web.config 文件相关),或者是否可以手动触发预加载配置文件的合并?我试图避免在我的任何自定义配置对象中支持自定义合并,并且可能忘记支持 System.Configuration...

中的现有设置

更新

为了回应现有的答案和评论,我想做一个重要的澄清。我能够从当前应用程序设置中加载 ConfigurationSection 个对象 (app.config / web.config)以及来自我的 custom.config 的物理文件。我需要知道是否有机会通过框架中的一些内置方式合并这些对象而不诉诸反射和 属性-by-属性 比较。


注意: 如果有一个适用于 .NET 3.0+ 的解决方案,我将不胜感激。如果您的答案针对更高版本的框架,请添加备注。

The .NET configuration system is not super flexible

这条评论一针见血,解释了为什么您找了很长时间但还没有找到任何东西。并非所有 .NET Framework 部分都是 "good",System.Configuration 值得放在最底部。对于最终很简单的任务,但同时又变成了极其僵化的东西,它被过度设计得荒谬可笑。很难对这是如何发生的进行逆向工程,我 认为 它因安全问题而瘫痪。也许有点可以理解,用数据征用一个程序总是一个相当大的风险。

我所知道的唯一扩展点是编写您自己的扩展点 SettingsProvider. The framework has only one for general usage, the LocalFileSettingProvider class. Also exceedingly inflexible, there isn't any way to alter its behavior. There is a decent example available for a custom setting provider, the RegistrySettingsProvider sample 演示了在注册表中存储设置的提供程序。它可以是您自己编写的一个很好的起点。

也许不完全是您的想法,请尝试一下您可以打破内部分层的想法System.Configuration。

实际上默认有3个配置继承级别:Machine、Exe和User(可以是Roaming或Local)。如果您自己加载配置文件,您可以使用 ExeConfigurationFileMap class in combination with ConfigurationManager.OpenMappedExeConfiguration 加载您自己的自定义配置层次结构。

我认为您不能更改 ConfigurationManager class 的默认路径,但您将获得一个 Configuration 元素,该元素可用于从加载的配置层次结构中获取任何部分。

如果您查看 How to read configSections 的答案,其中包含一些关于确定在层次结构中声明的部分级别的注释(使用 SectionInformation)

var localSections = cfg.Sections.Cast<ConfigurationSection>()
       .Where(s => s.SectionInformation.IsDeclared);

看看下面的代码就知道加载你的配置或者exe配置了。 一旦您清楚了以下代码,您就可以根据需要自定义加载和合并(加载两次并用另一个覆盖)。

private static void InitConfiguration()
{
    var map = new ExeConfigurationFileMap();
    var AssemblyConfigFile = "";
    if (File.Exists(AssemblyConfigFile))
        map.ExeConfigFilename = AssemblyConfigFile;
    else
        map.ExeConfigFilename = Path.Combine(Environment.CurrentDirectory, Environment.GetCommandLineArgs()[0]+".config");

    var Configuration = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);

    var serviceModelSectionGroup = ServiceModelSectionGroup.GetSectionGroup(Configuration);

}

正如 silver 指出的那样,配置良好的 ExeConfigurationFileMap 可以完成这项工作,但要付出一定的代价。

我已经采用了他的示例并制作了它的工作版本。

这是我为测试目的合并的两个配置文件:

custom.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="custom" type="..." />
  </configSections>
  <custom>
    <singleProperty id="main" value="BaseValue" />
    <propertyCollection>
      <property id="1" value="One" />
      <property id="4" value="Four" />
    </propertyCollection>
  </custom>
</configuration>

app.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="custom" type="..."/>
  </configSections>
  <custom>
    <singleProperty id="main" value="OverriddenValue" />
    <propertyCollection>
      <property id="1" value="OverridenOne" />
      <property id="2" value="Two" />
      <property id="3" value="Three" />
    </propertyCollection>
  </custom>
</configuration>

并且我使用了以下代码来测试合并后的设置:

var map = new ExeConfigurationFileMap();
map.MachineConfigFilename = PathToCustomConfig;
map.ExeConfigFilename = PathToAppConfig;
                
var configuration = ConfigurationManager.OpenMappedExeConfiguration(
        map, 
        ConfigurationUserLevel.None);
var section = configuration.GetSection("custom") as CustomConfigSection;
Assert.IsNotNull(section);

Assert.AreEqual(section.SingleProperty.Value, "OverriddenValue");
Assert.AreEqual(section.PropertyCollection.Count, 4);
// Needed to map the properties as dictionary, not to rely on the property order
var values = section.PropertyCollection
        .Cast<SimpleConfigElement>()
        .ToDictionary(x => x.ID, x => x.Value);
Assert.AreEqual(values["1"], "OverridenOne");
Assert.AreEqual(values["2"], "Two");
Assert.AreEqual(values["3"], "Three");
Assert.AreEqual(values["4"], "Four");

这种方法的优点

  • 我使用了内置的合并逻辑
  • 适用于旧版本的 .NET(在 3.5 上测试)
  • 不需要反射或其他黑魔法东西来触发行为。

缺点

  • 不太确定,但通过设置 map.MachineConfigFilename = PathToCustomConfig;,我假设我正在删除由真正的 machine.config 文件设置的任何值。这可能容易出错,对于 Web 应用程序应该避免,因为它们中的大多数都依赖于真实 machine.config
  • 中的内容
  • 需要传递应用程序配置文件的位置,因为它不再自动确定。因此需要弄清楚编译代码时app.config将如何命名(通常是AssemblyName.exe.config)
  • 您只能合并 两个 文件的内容。如果需要更大的层次结构,那么这将不会很好地工作。

我还在完善技术,所以我会return更新这个post一旦完成。

我们可能需要使用 类 ConfigurationSection 和 ConfigurationProperty

具体实施请参考下文link https://blog.danskingdom.com/adding-and-accessing-custom-sections-in-your-c-app-config/