关于c#:在.NET中运行时合并自定义配置节

Merging custom configuration sections at runtime in .NET

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

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

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

  • 机器配置
    • app.config(覆盖/合并machine.config中的各个元素)

我的目标是创建多个配置层,以便在machine.config和app.config文件之间(如果可能,在之后)可能存在其他配置文件:

  • 机器配置
    • custom.config(自定义配置干扰machine.config和app.config文件)
      • app.config-现在app.config与machine.config和custom.config合并。

更新

要点是-如果我在custom.config和app.config中都定义了配置部分,那么当我调用ConfigurationManager.GetSection("MyCustomSection")时,需要获得两个配置的合并版本。理想情况下,如果我能够像本文中描述的那样执行合并,那将是非常好的。

通常,我会编写自己的Configuration Manager类,并尽我所能获得所需的结果,但是假设.NET框架在machine.config和app.config中具有此功能,我认为我可能会从框架的内置功能中获益。此外,如果我真的应该求助于自己的配置管理器实现,那么我不知道如何手动触发这种合并。

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

更新

为了回应现有的答案和评论,我想做一个重要的澄清。我可以从当前的应用程序设置(app.config/web.config)和自定义的物理文件加载ConfigurationSection对象。我需要知道是否有机会合并这些对象,而不借助于反射和属性间的比较,以及框架中的一些内置方法。

注意:我希望有更好的解决方案可以在.NET 3.0+上工作。如果您的答案针对的是更高版本的框架,请添加注释。

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


0

这条评论将其钉住,并解释了为什么你一直在寻找很长时间,还没有找到任何东西。并非所有的.NET框架部分都是"好的",系统配置应该放在最底层。它被荒谬地过度设计为最终是一个简单的任务,但同时变成了一个极其不灵活的东西。很难对这件事进行逆向工程,我认为它因为安全问题而瘫痪了。也许可以理解,霸占一个程序的数据总是一个相当大的风险。

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

也许并不是你所想的那样,请记住你可以进入System.Configuration内部的分层。

  • 感谢您的诚实回答,我很感谢示例链接,我将调查它。我承认我有点失望,因为我知道它确实是如此的不灵活,系统配置。这似乎真的对我的目标有潜力,但似乎是"很难得到"。如果我不是那么顽固的话,我现在应该考虑其他的选择了,但那是另一个故事。
  • 我已经更新了我的帖子,顺便问一句,我提到的场景有什么不同吗,或者仍然是不可能的。
  • 嗯,你真的很快就达到了一个点,在那里你再也不用用它了。自己搜索特定的.config文件无疑是你永远不想做的事情。


正如Silver所指出的,配置良好的ExeConfigurationFileMap可以以一定的成本完成这项工作。

我以他的例子为例,做了一个有效的版本。

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

Cuff.CONFIG

2

App.CONFIG

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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>

我使用以下代码测试合并的设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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时,我会收到无效的强制转换错误。有什么想法吗?


查看以下代码,了解如何加载配置或exe配置。一旦清除了以下代码,您就可以根据需要自定义加载和合并(加载两次,并用另一个重写)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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帖子来回答这个问题。如果你能详细说明其中的一些缺点,我会很高兴的,特别是如果我丢失了machine.config中的内容,并且我可以合并两个以上的文件。我试着和RoamingUserConfigFilename合作,但似乎不管用。
  • 如果你在机器配置和应用配置中有相同的键,那么你的应用将获得你的应用值。您可以使用:configurationmanager.appsettings["keyname"];在这种合并之后,您仅在计算机配置中拥有的值也将可用。
  • 重点是,当我创建ExeConfigurationFileMap时,我明确地说是map.MachineConfigFilename = PathToCustomConfig。重点是exe配置与我所说的机器配置合并在一起。这是不是应该忽略真实的machine.config呢,还是我遗漏了什么?
  • 你说得对,这没有意义,我不知道为什么它能工作,但事实是,在machine.config中存在的,并且没有出现在你的配置中的值将在该覆盖之后可用。(我也试过对execonfigurationfilemap使用不同的构造函数)如果您找到它工作的原因,请告诉我。
  • 我将尝试调查为什么会这样,但我的猜测是有一个内部的.NET逻辑可以确保始终考虑机器配置。我想有些设置不应该通过自定义的app/web配置文件来使用,如果没有这些设置,.net框架可能无法运行。事实上,我很高兴它仍然被考虑在内,因为我也可以从Web应用程序中的方法中获益。


默认情况下,配置继承实际上有三个级别:machine、exe和user(可以是漫游或本地)。如果您自己加载配置文件,则可以将ExeConfigurationFileMap类与ConfigurationManager.OpenMappedExeConfiguration结合使用来加载您自己的自定义配置层次结构。

我不认为您可以用这个更改configurationManager类的默认路径,但是您将得到一个配置元素,它可以用来从加载的配置层次结构中获取任何部分。

如果您查看了如何读取配置节的答案,它将包含一些关于确定在层次结构中声明了哪些级别的节的注释(使用节信息)。

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

  • 虽然这不能回答我的问题,但我必须感谢您提供的提示-如果我能够检测到在custom.config和app.config中声明了哪些节(您的答案似乎很好地解决了这一部分),我将已经拥有了需要合并的确切节对象。现在我应该找到一种方法来触发…合并。
  • 我的错,你似乎是间接地回答我的问题,因为江户十一号〔0〕确实是我的选择,所以按我想要的方式使用它似乎有点错。不过,我担心我很难找到另一个选择,你应该得到一些赞扬,或者把这个指向我。