.NET配置文件在写入时损坏

本文探讨了.NET框架在以编程方式修改配置文件时可能导致的问题,特别是涉及自定义部分和部分组时。当尝试添加新的部分组时,配置文件可能会变得无效,因为.NET保存机制错误地在配置文件的声明部分中写入了新组。解决方案是通过后处理步骤手动调整XML以修复结构。文章强调了.NET配置处理的底层机制及其潜在的不直观行为,提醒开发者注意配置文件的正确管理和更新。
摘要由CSDN通过智能技术生成

目录

介绍

背景

问题

.NET深入探讨

公共API:

这就是问题所在!​

解决方案


.NET配置文件旨在使用自定义部分(System.Configuration.ConfigurationSection)和部分组(System.Configuration.ConfigurationSectionGroup)进行自定义。为此,开发人员将这些基类子类化以实现其自定义功能并将它们合并到配置文件中。.NET配置文件是XML文件并遵循定义的架构。它有点复杂,但本文中相关的架构元素是beginning ()的声明部分,它声明了配置文件中使用的部分和部分组的布局和类型。这允许.NET配置机制使用配置信息正确地实例化您的自定义类。以编程方式修改配置文件时,有时,添加新的部分组会在重写时损坏配置文件,从而防止将来读取它。本文就是对这种情况的一个案例研究。

介绍

我们有一个Windows服务应用程序,它由从应用程序配置文件中读取的配置信息驱动。该服务的目的是在预先安排的时间运行各种步骤来执行诸如生成静态/缓存数据表之类的事情。这些步骤在步骤组内按顺序运行,而步骤组并行运行。该服务旨在通过配置文件在执行参数和步骤组的定义以及每个组内的步骤中进行定制。为了帮助管理配置文件,开发了一个Windows GUI应用程序。将新步骤组添加到现有配置文件并重新保存时,生成的配置文件会损坏,从而阻止将来读取它。.NET机制写出的内容.NET机制无法读回。

背景

.NET配置文件是XML文件,并遵循允许自定义的已定义架构。有许多标准配置结构旨在集成.NET支持的各种功能,例如应用程序设置、运行时序列化、服务模型等。自定义是通过在开头声明自定义部分(System.Configuration.ConfigurationSection)和部分组(System.Configuration.ConfigurationSectionGroup)来实现的配置文件如下:

<configuration>
  <configSections>
            <!-- Custom Declarations Go Here... -->
  </configSections>
</configuration>

在我们的例子中,原始配置文件(在尝试添加新组之前)看起来像:

<configuration>
  <configSections>
    <sectionGroup name="ServiceConfiguration" type="System.Configuration.ConfigurationSectionGroup, System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" >

      <section name="Administrators" type="My.CustomSection" />

      <sectionGroup name="ServiceStepGroups" type="My.StepGroups" >

        <sectionGroup name="MyGroup1" type="My.StepGroup" />

      </sectionGroup>
    </sectionGroup>
  </configSections>

  <ServiceConfiguration>
    <Administrators ... />

    <ServiceStepGroups>

      <MyGroup1 ... />

    </ServiceStepGroups>

  </ServiceConfiguration>
</configuration>

在这里,我们在configSections顶级自定义部分组ServiceConfiguration中声明,该部分组包含我们的服务应用程序独有的所有业务类型配置信息。然后,我们有一个与本次讨论无关的管理员部分,但保留为可视化以演示如何定义自定义部分。

被定义为一个自定义部分组,该ServiceStepGroups组又将托管其他部分组,例如MyGroup1,每个部分组都将定义该组中的步骤。 

 configSections 声明之后,我们从 ServiceConfiguration 定义开始。内容为简洁省略,不相关。相关的要求是 1)自定义部分和部分组必须同时声明和定义,2)定义布局的结构必须与声明的结构相匹配,以及 3)部分组必须具有唯一的名称,即使它们是相同的.NET类型。

问题

在代码中以编程方式添加一个新的 My.StepGroup (MyGroup2) ServiceStepGroups中,然后重新保存会破坏配置文件,因为它违反了要求23(前面提到过),如下所示:

<configuration>
  <configSections>
    <sectionGroup name="ServiceConfiguration" type="System.Configuration.ConfigurationSectionGroup, System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" >

      <section name="Administrators" type="My.CustomSection" />

      <sectionGroup name="ServiceStepGroups" type="My.StepGroups" >

        <sectionGroup name="MyGroup1" type="My.StepGroup" />

      </sectionGroup>
    </sectionGroup>
    <sectionGroup name="ServiceConfiguration">  <!-- No type attribute, duplicate name and wrong place -->

      <sectionGroup name="ServiceStepGroups">   <!-- No type attribute, duplicate name and wrong place -->

        <sectionGroup name="MyGroup2" type="My.StepGroup" />

      </sectionGroup>
    </sectionGroup>
  </configSections>

  <ServiceConfiguration>
    <Administrators ... />

    <ServiceStepGroups>

      <MyGroup1 ... />
      <MyGroup2 ... />     <!-- This is in expected place -->

    </ServiceStepGroups>

  </ServiceConfiguration>
</configuration>

.NET深入探讨

经过艰苦的努力,我能够理解发生了什么。通常,我会将此行为视为.NET代码的错误,因为它无法读取它写出的内容;但是,代码似乎是结构化的,旨在产生这种行为;因此,该行为似乎不是一个错误,只是一个带有不直观行为的疼痛和无证设计。由于这是一种奇怪、无证和不直观的行为,我觉得有必要与后代分享我的发现。

以下是我发现的内容:

公共API

System.Configuration.ConfigurationSectionGroup

这是用于对部分和其他组进行分组的配置结构,本质上是一个容器。它可以被子类化,但它本身从无到有并且提供的功能很少,因此它几乎没有提供定制的机会。

System.Configuration.Configuration

ConfigurationSectionGroup相似,因为它本质上充当了部分和组的容器,并且无中生有;但是,它是密封的,不能被子类化。它提供上下文信息、一些用于处理连接字符串和应用程序设置的基本结构以及用于将配置保存到文件的API。它通过委托操作为自定义提供了一些灵活性。

因此,总的来说,配置文件的大部分处理是在幕后进行的,并且在.NET框架内部。因此,研究这些机制需要查看.NET代码库并在VS调试器中反编译必要的代码。

System.Configuration.MgmtConfigurationRecord
System.Configuration.BaseConfigurationRecord

这些类是.NET框架的内部类,并提供了配置管理的大部分内容。简单地说,每个配置[文件]都与一个配置记录相关联。配置记录提供了配置文件中的内容以及配置信息的写入方式的簿记。

SaveAs(string filename, ConfigurationSaveMode saveMode, bool forceUpdateAll)

每当用户想要保存配置文件时,都会调用此方法。它管理保存过程,本质上是提供保存功能的包装器并提供错误处理。它调用CopyConfig(...),此方法的工作是复制所有原始配置文件内容(注释和所有)并集成新的更改。它调用以下方法:

CopyConfigDeclarationsRecursive(...)

该方法从原始配置文件的声明部分复制所有内容。然后它调用

WriteUnwrittenConfigDeclarations(...)

它写入任何新的声明。

这就是问题所在!

新的部分和部分组是在现有部分和组之后编写的。

直觉,是吗?错误的!

再次考虑之前介绍的损坏配置文件:

<configuration>
  <configSections>
    <sectionGroup name="ServiceConfiguration" type="System.Configuration.ConfigurationSectionGroup, System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" >

      <section name="Administrators" type="My.CustomSection" />

      <sectionGroup name="ServiceStepGroups" type="My.StepGroups" >

        <sectionGroup name="MyGroup1" type="My.StepGroup" />

      </sectionGroup>
    </sectionGroup>
    <sectionGroup name="ServiceConfiguration">  <!-- No type attribute, duplicate name and wrong place -->

      <sectionGroup name="ServiceStepGroups">   <!-- No type attribute, duplicate name and wrong place -->

        <sectionGroup name="MyGroup2" type="My.StepGroup" />

      </sectionGroup>
    </sectionGroup>
  </configSections>

  <ServiceConfiguration>
    <Administrators ... />

    <ServiceStepGroups>

      <MyGroup1 ... />
      <MyGroup2 ... />     <!-- This is in expected place -->

    </ServiceStepGroups>

  </ServiceConfiguration>
</configuration>

我们有一个主要的自定义部分组ServiceConfiguration,它包含所有业务类型的配置信息,这些信息是唯一的或特定于应用程序的。在这个主组中,我们有一个ServiceStepGroups组,它依次声明应用程序要执行的各种步骤组(MyGroup1MyGroup2)。

但是,有两个 (2) ServiceConfiguration声明,这明显违反了需要唯一命名组的配置规范。在原始配置文件中,我们只定义了MyGroup1,它和它所包含的组被type属性声明的位置,这是在加载配置文件时声明.NET组类型所必需的。但是,我们添加了MyGroup2加载配置文件后,我们可以使用新组重新保存配置文件。

这里开始了不直观的行为

因为CopyConfigDeclarationsRecursive(...)首先被调用,所以它会写入一个从原始文件复制的完整ServiceConfiguration声明。当WriteUnwrittenConfigDeclarations(...)被调用时,它别无选择,只能编写第二个ServiceConfiguration声明以维护声明层次结构;但是,它在没有type属性的情况下这样做,大概是因为写入时不需要.NET类型,而仅在读取时才需要。如果原始声明中不存在声明(例如ServiceStepGroups),则写入类型;如果存在声明,则不写入类型。

所以,这种行为在SaveAs(...)中似乎是一个设计缺陷。但是,为了保持开放的心态,我将保留永恒的判断,因为可能存在相互竞争的目标或其他我不知道的行为场景,它们推动了微软的设计。尽管如此,我认为我们都同意更好的文档将有助于理解以编程方式修改和保存配置文件的行为和功能。如果有更好的文档,我找不到它。

解决方案

毕竟被发现并理解我能够通过实施后处理步骤来生成正确的配置文件。这是一种解决方法:手动修改新配置文件的XML(我使用了System.Xml库),将新组声明移动到原始ServiceStepGroups声明中的适当位置,并删除重复的分层结构。

一开始我本可以这样做,但我认为我不需要(基于对直观功能和缺乏文档的期望)并且问题在于我和我的代码使用.NET配置机制。虽然在技术上证明是正确的,但它是.NET 配置处理的底层机制及其局限性的一个很好的(但令人痛苦的)教训。

因此,以积极的态度结束也许这个技术潜水和案例研究可以提供一些文档或参考其他人在未来遇到类似问题。我想起约翰亚当斯的一句话:

引用:

后人!你永远不会知道这一代人为维护你的自由付出了多少代价!我希望你能好好利用它。如果你不这样做,我将在天堂忏悔我曾经付出了一半的努力来保护它。

后人,我希望你好好利用这些信息。

https://www.codeproject.com/Tips/5316335/NET-Configuration-File-Corrupts-On-Write

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值