App.config добавить вложенную группу к существующему узлу

Я должен сохранить две разные группы настроек в моей группе настроек root. Он должен выглядеть так:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>    
    <sectionGroup name="ROOT_GROUP">
       <sectionGroup name="GROUP_1">
         ........................
         some_settings
         ........................
       </sectionGroup>
       <sectionGroup name="GROUP_2">
         ........................
         some_other_settings
         ........................
       </sectionGroup>
     </sectionGroup>
  </configSections>
................................
other_system_tags
................................
</configuration>

Нюанс заключается в том, что я должен сохранять его один за другим в разных местах моего кода. (Например, GROUP_1 может быть строкой соединения, а GROUP_2 - это некоторые настройки среды, и они оба вместе заполняются пользователями в разных разделах моего приложения)

Я сделал этот простой тестовый класс для получения ожидаемого результата

[TestFixture]
public class Tttt
{
    private string ROOT_GROUP = "ROOT_GROUP";
    private string GROUP_1 = "GROUP_1";
    private string GROUP_2 = "GROUP_2";

    [Test]
    public void SaveSettingsGroups()
    {
        SaveGroup1();
        SaveGroup2();
        Assert.True(true);
    }

    private Configuration GetConfig()
    {
        var configFilePath = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
        var map = new ExeConfigurationFileMap { ExeConfigFilename = configFilePath };
        var config = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);
        return config;
    }

    private void SaveGroup1()
    {
        var config = GetConfig();

        var root = new UserSettingsGroup();

        config.SectionGroups.Add(ROOT_GROUP, root);

        config.Save(ConfigurationSaveMode.Modified);
        ConfigurationManager.RefreshSection(root.Name);

        var nested = new UserSettingsGroup();

        root.SectionGroups.Add(GROUP_1, nested);

        config.Save(ConfigurationSaveMode.Modified);
        ConfigurationManager.RefreshSection(nested.Name);             
    }

    private void SaveGroup2()
    {
        var config = GetConfig();

        var root = config.GetSectionGroup(ROOT_GROUP);

        var nested = new UserSettingsGroup();
        root.SectionGroups.Add(GROUP_2, nested);

        config.Save(ConfigurationSaveMode.Modified);
        ConfigurationManager.RefreshSection(nested.Name);
    }
}

НО почему-то результат этого кода отличается

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>    
    <sectionGroup name="ROOT_GROUP">
      <sectionGroup name="GROUP_1">
        ........................
        some_settings
        ........................
      </sectionGroup>
    </sectionGroup>
    <sectionGroup name="ROOT_GROUP">
      <sectionGroup name="GROUP_2">
      ........................
      some_other_settings
      ........................
      </sectionGroup>
    </sectionGroup>
  </configSections>
................................
other_system_tags
................................
</configuration>

Узел ROOT_GROUP дублируется, и, конечно, визуальная студия бросает мне исключение, что ROOT_GROUP уже существует. Очевидно, моя проблема скрыта в методе SaveGroup2(), когда я добавляю новую вложенную группу в существующую корневую группу, а затем сохраняю ее - но почему?

UPD Я только что добавил новый метод

    private void SaveGroup3()
    {
        var config = GetConfig();

        var root = config.GetSectionGroup(ROOT_GROUP);
        var nested1 = root.SectionGroups.Get(0);

        var nested2 = new UserSettingsGroup();
        var nested3 = new UserSettingsGroup();

        nested1.SectionGroups.Add("GROUP_2", nested2);
        root.SectionGroups.Add("GROUP_3", nested3);
        config.Save(ConfigurationSaveMode.Modified);
        ConfigurationManager.RefreshSection(nested2.Name);
        ConfigurationManager.RefreshSection(nested3.Name);
    }

И замените его в тесте

[Test]
public void SaveSettingsGroups()
{
    SaveGroup1();
    SaveGroup3();
    Assert.True(true);
}

И получил это странное поведение

  <sectionGroup name="ROOT_GROUP">
    <sectionGroup name="GROUP_1">
      <sectionGroup name="GROUP_2">
      </sectionGroup>
    </sectionGroup>
    <sectionGroup name="GROUP_3">
    </sectionGroup>
  </sectionGroup>

Как вы можете видеть, странность в том, что результат полностью ожидаемый. ROOT_GROUP не дублировался, так как мне это нужно, но почему это происходит в SaveGroup2()? Я что-то пропустил в SaveGroup2()?

UPD2 - HACK

Просто попробовал простую идею - что, если бы я очистил root_group, прежде чем добавлять в нее новый вложенный элемент?

    private void SaveGroup2()
    {
        var config = GetConfig();

        var root = config.GetSectionGroup(ROOT_GROUP);

        var nested = new ConfigurationSectionGroup();

        //Copy exiting nested groups to array
        var gr = new ConfigurationSectionGroup[5];       
        root.SectionGroups.CopyTo(gr,0);
        gr[1] = nested;
        //<!----

        root.SectionGroups.Clear();

        config.Save(ConfigurationSaveMode.Modified);
        ConfigurationManager.RefreshSection(root.Name);

        root.SectionGroups.Add(gr[0].Name, gr[0]);
        root.SectionGroups.Add(GROUP_2, gr[1]);

        config.Save(ConfigurationSaveMode.Modified);
        ConfigurationManager.RefreshSection(root.Name);
    }

И как вы, наверное, догадались - это работает!

<sectionGroup name="ROOT_GROUP">
  <sectionGroup name="GROUP_1" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
  </sectionGroup>
  <sectionGroup name="GROUP_2" type="System.Configuration.ConfigurationSectionGroup, System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" >
  </sectionGroup>
</sectionGroup>

Я думаю, что это похоже на ошибку или есть некоторые скрытые вещи, которые я пропустил. Может кто-нибудь объяснить мне, что я сделал неправильно?

Ответ 1

Мне потребовалось некоторое время, чтобы понять, что происходит, и мне кажется, что есть проблема с самим кодом рамки, в частности, метод WriteUnwrittenConfigDeclarationsRecursive(SectionUpdates declarationUpdates, XmlUtilWriter utilWriter, int linePosition, int indent, bool skipFirstIndent) внутри класса MgmtConfigurationRecord. Я не хочу писать длинную историю, но если вы хотите, вы можете отлаживать.Net framework code и сами убедиться.

Вы можете исправить свой код следующими способами:

1. Сохраните все группы вместе

private void SaveGroups()
{
    var config = GetConfig();
    var root = new ConfigurationSectionGroup();
    config.SectionGroups.Add(ROOT_GROUP, root);
    config.Save(ConfigurationSaveMode.Modified);
    ConfigurationManager.RefreshSection(root.Name);

    var nested = new UserSettingsGroup();
    root.SectionGroups.Add(GROUP_1, nested);

    nested = new UserSettingsGroup(); 
    root.SectionGroups.Add(GROUP_2, nested);

    config.Save(ConfigurationSaveMode.Modified);
    ConfigurationManager.RefreshSection(root.Name);
}

2. Удалите существующие элементы группы перед добавлением нового

private void SaveGroup2()
{
    var config = GetConfig();
    var root = config.SectionGroups[ROOT_GROUP];
    var existingGroups = new Dictionary<string, ConfigurationSectionGroup>();
    while (root.SectionGroups.Count > 0)
    {
        existingGroups.Add(root.SectionGroups.Keys[0], root.SectionGroups[0]);
        root.SectionGroups.RemoveAt(0);
    }

    config.Save(ConfigurationSaveMode.Modified);

    existingGroups.Add(GROUP_2, new UserSettingsGroup());
    foreach (var key in existingGroups.Keys)
    {
        existingGroups[key].ForceDeclaration(true);
        root.SectionGroups.Add(key, existingGroups[key]);
    }

    config.Save(ConfigurationSaveMode.Modified);
    ConfigurationManager.RefreshSection(root.Name);
}

Ответ 2

В первом обновлении вы добавили GROUP_2 под первой записью под root:

    //nested1 is now the first entry under root due to Get(0)
    var nested1 = root.SectionGroups.Get(0); 

    var nested2 = new UserSettingsGroup();
    var nested3 = new UserSettingsGroup();

    //I think you meant root here instead of nested1.
    nested1.SectionGroups.Add("GROUP_2", nested2);