.netpersistenceconfiguration-filessettings

How to load a separate Application Settings file dynamically and merge with current settings?


There are questions pertaining to reading settings from a separate config file and others similar to it, but my question is specific to application property settings (i.e. <MyApplication.Properties.Settings> - see XML file below) and how to load them dynamically. I tried the method in this post, which involved refreshing the entire appSettings section of the main config file, but my adaptation threw exceptions because I wasn't replacing the appSettings section:

var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal);
// Have tried the other ConfigurationUserLevels to no avail
config.AppSettings.File = myRuntimeConfigFilePath;
config.Save(ConfigurationSaveMode.Modified); // throws ConfigurationErrorsException
ConfigurationManager.RefreshSection("userSettings");

The ConfigurationErrorsException.Message is "The root element must match the name of the section referencing the file, 'appSettings' (C:\myFile.xml line 2)." The file is:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <userSettings>
        <MyApplication.Properties.Settings>
            <setting name="SineWaveFrequency" serializeAs="String">
                <value>6</value>
            </setting>
            <setting name="SineWaveAmplitude" serializeAs="String">
                <value>6</value>
            </setting>
        </MyApplication.Properties.Settings>
    </userSettings>
</configuration>

Is there a way to import the values from this file into the MyApplication.Properties.Settings.Default class, with the framework handling all XML deserialization like it does when the config file is loaded on application startup?


Solution

  • Well, this works:

    using System;
    using System.Configuration;
    using System.IO;
    using System.Linq;
    using System.Xml.Linq;
    using System.Xml.XPath;
    
    public static class SettingsIO
    {
        internal static void Import(string settingsFilePath)
        {
            if (!File.Exists(settingsFilePath))
            {
                throw new FileNotFoundException();
            }
    
            var appSettings = Properties.Settings.Default;
            try
            {
                var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal);
    
                string appSettingsXmlName = Properties.Settings.Default.Context["GroupName"].ToString(); // returns "MyApplication.Properties.Settings";
    
                // Open settings file as XML
                var import = XDocument.Load(settingsFilePath);
                
                // Get the whole XML inside the settings node
                var settings = import.XPathSelectElements("//" + appSettingsXmlName);
    
                config.GetSectionGroup("userSettings")
                    .Sections[appSettingsXmlName]
                    .SectionInformation
                    .SetRawXml(settings.Single().ToString());
                    
                config.Save(ConfigurationSaveMode.Modified);
                ConfigurationManager.RefreshSection("userSettings");
    
                appSettings.Reload();
            }
            catch (Exception) // Should make this more specific
            {
                // Could not import settings.
                appSettings.Reload(); // from last set saved, not defaults
            }
        }
    
        internal static void Export(string settingsFilePath)
        {
            Properties.Settings.Default.Save();
            var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal);
            config.SaveAs(settingsFilePath);
        }
    }
    

    The export method creates a file like the following:

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
        <userSettings>
            <MyApplication.Properties.Settings>
                <setting name="SineWaveFrequency" serializeAs="String">
                    <value>1</value>
                </setting>
                <setting name="SineWaveAmplitude" serializeAs="String">
                    <value>100</value>
                </setting>
                <setting name="AdcShift" serializeAs="String">
                    <value>8</value>
                </setting>
                <setting name="ControlBitsCheckedIndices" serializeAs="String">
                    <value>0,1,2,3,5,6,7,8</value>
                </setting>
                <setting name="UpgradeSettings" serializeAs="String">
                    <value>False</value>
                </setting>
            </MyApplication.Properties.Settings>
        </userSettings>
    </configuration>
    

    The import method parses that file, takes the everything inside the <MyApplication.Properties.Settings> node, puts that XML into the user.config file at the appropriate section, then reloads the Properties.Settings.Default in order to grab those values from the new user.config file.