【问题标题】:Merging custom configuration sections at runtime in .NET在 .NET 中在运行时合并自定义配置部分
【发布时间】:2020-09-04 17:25:17
【问题描述】:

我的问题是关于使用标准 .NET 配置对象和自定义配置元素(可以通过扩展 System.Configuration.ConfigurationSection 类来定义)。我们通常从System.Configuration.ConfigurationManager类的方法中获取这些,GetSection(...)就是一个例子。

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

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

  • ma​​chine.config
    • app.config(覆盖/合并 machine.config 中的相应元素)

我的目标是创建多层配置,以便在 ma​​chine.configapp.config 之间(如果可能的话,之后)可能还有其他配置文件强>文件:

  • ma​​chine.config
    • custom.config(在 machine.config 和 app.config 文件之间产生干扰的自定义配置)
      • app.config - 现在 app.configma​​chine.configcustom.config 合并

更新

重点是 - 如果我在 custom.configapp.config 中都定义了配置部分,我需要获得两个配置的合并版本当我打电话给ConfigurationManager.GetSection("MyCustomSection")。理想情况下,如果我能够按照this MSDN article 中的描述执行合并,那就太好了。


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

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


更新

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


注意:如果有更好的解决方案可以在 .NET 3.0+ 上运行,我将不胜感激。如果您的答案针对的是更高版本的框架,请添加注释。

【问题讨论】:

  • 我期待看到这个问题的答案。但我担心你所要求的是不可能的。 .NET 配置系统在配置文件拆分/模块化方面不是超级灵活。
  • @stakx,也许你是对的。尽管如此,由于 .NET 框架本身公开了合并功能(并在内部进行合并),因此至少必须能够理解这一切是如何发生的。在我的大多数项目中,我已经为简化配置做了很多工作,但这种合并功能仍然是我长期以来一直在寻找的难题中缺失的部分。
  • 这不是问题的确切答案,但您可以使用具有 configSource 属性的外部卫星配置文件:msdn.microsoft.com/library/…
  • @SimonMourier,谢谢你的提示。但是,这似乎有助于将配置文件拆分到更方便的位置(除非我遗漏了什么)。我对在具有相似结构的不同配置文件之间分层设置更感兴趣 - 例如,如果 app.config 和 custom.config 都定义了元素“自定义元素”,我需要从两个文件中获取它的合并版本。当我使用ConfigurationManager.GetSection(...)。我希望这能进一步阐明我的要求。

标签: c# .net configuration configurationmanager


【解决方案1】:

.NET 配置系统不是超级灵活

该评论指出了这一点,并解释了为什么您已经寻找了很长时间但还没有找到任何东西。并非所有 .NET Framework 部分都是“好”的,System.Configuration 应该放在最底层。对于最终是一项简单的任务但同时变成极其不灵活的事情,它被过度设计了。很难对这是如何发生的进行逆向工程,我认为它因安全问题而瘫痪。或许可以理解,用数据征用程序总是有相当大的风险。

我知道的唯一扩展点是编写你自己的SettingsProvider。该框架只有一个用于一般用途的 LocalFileSettingProvider 类。同样非常不灵活,没有任何方法可以改变它的行为。自定义设置提供程序有一个不错的示例,RegistrySettingsProvider sample 演示了将设置存储在注册表中的提供程序。这可能是编写自己的一个很好的起点。

也许不完全是您的想法,请不要考虑您可以打破 System.Configuration 内部分层的想法。

【讨论】:

  • 感谢您的诚实回答,我很欣赏示例链接,我会研究一下。我承认我有点失望地得知它确实如此不灵活,系统配置。它似乎真的有我的目标的潜力,但好像它是“努力获得”。如果我不是那么固执的话,我现在应该考虑替代方案了……但那是另一个故事
  • 我已经更新了我的帖子,顺便说一句。我现在提到的场景有什么不同吗,或者仍然是不可能的。
  • Hmya,你很快就达到了使用它完全没有意义的地步。自己寻找特定的 .config 文件肯定是您永远真正想做的事情。
【解决方案2】:

正如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)
  • 您可以通过这种方式合并两个文件的内容。如果需要更大的层次结构,那么这将无法正常工作。

我还在完善这项技术,因此我会在完成后返回更新这篇文章。

【讨论】:

  • 这如何与 appSettings 一起使用?尝试加载 appsettings 时出现无效转换错误。有什么想法吗?
【解决方案3】:

看看下面的代码,它知道加载你的配置或 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);

}

【讨论】:

  • 嗨,你的想法似乎让我更接近我的愿望。查看我的wiki post 这个问题。如果您能详细说明一些缺点,我会很高兴 - 特别是如果我丢失了 machine.config 中的内容,并且我可以合并两个以上的文件。我尝试使用RoamingUserConfigFilename,但它似乎不起作用。
  • 如果您在机器配置和应用配置中具有相同的密钥,那么您的应用将获取您的应用值。您可以使用:ConfigurationManager.AppSettings["keyName"];在这种合并之后,您仅在机器配置中拥有的值也将可用。
  • 重点是,当我创建ExeConfigurationFileMap 时,我明确表示map.MachineConfigFilename = PathToCustomConfig。关键是exe配置与我所说的机器配置合并。这不应该忽略真正的machine.config 还是我错过了什么?
  • 你说得对,它没有意义,我不知道它为什么会起作用,但它是存在于 machine.config 中且未出现在您的配置中的值将是在覆盖之后可用。 (我也尝试对 ExeConfigurationFileMap 使用不同的构造函数)如果您找到它工作的原因,请告诉我。
  • 我会尝试调查为什么会这样,但我猜测是有一个内部 .NET 逻辑确保始终考虑机器配置。我想有些设置不能通过自定义应用程序/Web 配置文件获得,如果没有这些设置,.NET 框架可能无法运行。事实上,我很高兴它仍然被考虑在内,因为我也可以从 Web 应用程序中的方法中受益。
【解决方案4】:

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

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

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

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

【讨论】:

  • 虽然这不能回答我的问题,但我必须感谢它提供的提示 - 如果我能够检测到哪些部分已在 custom.config 和 app.config (似乎你的答案很好地解决了这部分问题),我已经有了我需要合并的确切部分对象。现在我应该找到一种方法来触发...合并。
  • 我的错,看来您实际上是在间接回答我的问题,因为 ExeConfigurationFileMap 确实是要走的路,以我想要的方式(ab)使用它似乎有点错误.不过,我担心我很难找到替代方案,你应该得到一些荣誉或指出这一点。
【解决方案5】:

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

有关实施的详细信息,请参阅以下链接 https://blog.danskingdom.com/adding-and-accessing-custom-sections-in-your-c-app-config/

【讨论】:

    猜你喜欢
    • 2013-09-21
    • 2012-08-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-28
    • 2010-10-25
    相关资源
    最近更新 更多