App.config add nested group to existing node
Asked Answered
L

2

11

I have to save 2 different groups of settings in my root settings group. It should looks like this:

<?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>

The Nuance is that I have to save it one after another in different places in my code. (For example, GROUP_1 can be a connection strings and GROUP_2 is some environment settings and they both together are filling by users in different sections of my application)

I made this simple test class to get the expected result

[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);
    }
}

BUT for some reason the result of this code is different

<?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>

The ROOT_GROUP node is duplicated and of course visual studio throws me an exception that ROOT_GROUP is already exists. Obviously, my problem is hidden in method SaveGroup2() when I add new nested group to existed root group and then save it - but why?

UPD I've just added new method

    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);
    }

And replace it in test

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

And got this strange behaviour

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

As you can see, the strangeness is in that the result is totally expected. ROOT_GROUP wasn't duplicate, as I needed it, but why it does in SaveGroup2()? Did I miss something in SaveGroup2()?

UPD2 - HACK

Just tried a simple idea - what if I would clear the root_group before adding a new nested element to it?

    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);
    }

And how do you probably guess - it works!

<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>

I think it looks like a bug or there are some hidden things that I missed. Can somebody explain me what did I do wrong?

Loreenlorelei answered 15/9, 2018 at 12:5 Comment(3)
I'm not going to earn my 100th (I know it only says 97) bounty by telling you the simple answer. Steer clear of the ExeConfigurationFileMap class or any Config altering classes, they all suck. The Config files are just XML. Use the XML classes, like LINQ to XML.Thom
That's definitely a bug. Even in your UPD 1, if your Group_1 had something inside and then you add sub SectionGroup to it, it will duplicate all Root Section. I would suggest you to use your custom config instead, as it must be that this functionality is not tested by Microsoft.Journeywork
@IgorTkachenko Thank you for your reply, I decided to abandon the root_group in my config. P.S.The Programmers world is very exciting, even Microsoft is subject to its laws cs8.pikabu.ru/post_img/2016/06/23/9/1466695766198687223.jpgLoreenlorelei
S
1

It took me a while to figure-out what was going on and tl;dr it seems, to me, there is a problem with the framework code itself, particularly method WriteUnwrittenConfigDeclarationsRecursive(SectionUpdates declarationUpdates, XmlUtilWriter utilWriter, int linePosition, int indent, bool skipFirstIndent) inside class MgmtConfigurationRecord. I don't want to write a long story but if you wish you could debug .Net framework code and see for yourself.

You can fix your code in following ways:

1. Save all groups together

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. Remove existing group items before adding a new one

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);
}
Soupy answered 25/9, 2018 at 21:19 Comment(0)
B
0

In your first update, you added GROUP_2 under the first entry under 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);
Burgos answered 19/9, 2018 at 19:11 Comment(2)
DId you try to reproduce my test? Your answer is meaninglessLoreenlorelei
Yes, and it's all working exactly how it is supposed to when coded correctly. I'm confused on where you are having problems understanding. My answer above is my best attempt to help you understand.Burgos

© 2022 - 2024 — McMap. All rights reserved.