Writing to .Net Config Files

I’ve been working with config files for quite some time.  I was recently reminded that I needed to finish my original article and share my final findings on my personal best practices for working with config files.

One of the coolest and most useful features in config files is the file attribute as displayed below (see my other article for a more detailed explaination, preferred-method-for-read-only-config-files).

configuration
      <section name="MyCustomSection" type="System.Configuration.NameValueFileSectionHandler, System, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>  

 

configuration

If you take a look at the other article, it’s a terrific way to setup a read only config file, but what about writing back to it at runtime?  Well, the best way is to use the ConfigurationManager object and then open the raw xml into an XmlDocument object.  (yes yes, I might be a Visual Basic MVP, but I can write the occasional C#)

Configuration config = ConfigurationManager.OpenExeConfiguration(FileName);
ConfigurationSection section = config.GetSection(SectionName);

XmlDocument xml = new XmlDocument();
xml.LoadXml(section.SectionInformation.GetRawXml());

But the problem is, if you have used the file attribute (file=”MyCustomConfigFile.config”), it’s not smart enough to grab the contents of the file (I’m hoping to contact the product team soon to see if they can address the issue).  Fortunately, there is a way to get the filename from the file attribute.  And by adding in a little recursion, we’re able to create an easy method to write back to the proper config file, without having to know the actual filename at runtime.

private const string SETTING_KEY_NAME = “key”;
private const string SETTING_VALUE_NAME = “value”;

public static void SetConfigValue(string SectionName, string KeyName, string Value)
        {
            SetConfigValue(System.Reflection.Assembly.GetEntryAssembly().Location , SectionName, KeyName, Value);
        }

        public static void SetConfigValue(string FileName, string SectionName, string KeyName, string Value)
        {
            Configuration config = ConfigurationManager.OpenExeConfiguration(FileName);
            ConfigurationSection section = config.GetSection(SectionName);

            XmlDocument xml = new XmlDocument();
            xml.LoadXml(section.SectionInformation.GetRawXml());

            if (xml.DocumentElement.Attributes[“file”] == null)
                {
                    foreach (XmlElement element in xml.ChildNodes[0])
                    {
                        if (element.Attributes[SETTING_KEY_NAME].Value == KeyName) element.Attributes[SETTING_VALUE_NAME].Value = Value;
                    };

                    section.SectionInformation.SetRawXml(xml.OuterXml);
                    config.Save();
                }
            else
                {
                    SetConfigValue(System.Reflection.Assembly.GetEntryAssembly().Location.Remove
                        (
                        System.Reflection.Assembly.GetEntryAssembly().Location.LastIndexOf(“\”)) + “\” + xml.DocumentElement.Attributes[“file”].Value.Remove
                            (
                            xml.DocumentElement.Attributes[“file”].Value.LastIndexOf(“.config”)
                            ),
                        SectionName,
                        KeyName,
                        Value);
                }
        }

This creates a new problem though.  You can no longer use the same format of the underlying file, specified in the file attribute.

Old format of the MyCustomConfigFile.config specified in my other article preferred-method-for-read-only-config-files:

    

New required format for writing to config files with the file attribute:

               

You’ll notice that I had a comment in the file specified in the file attritube. ()  I honestly can’t recall why this is required (I’m posting this article months after I did the work), but it has to do with reading the config file, and the filenames it is looking for.

This new referred config file format creates a new problem.  You can no longer use the reading method I used in the other article.  I’ve fixed it in the code below as well as provided the full class.

Below is the full class that I wrote.  All I ask is that you add a comment telling me how you used it.

using System.Configuration;
using System.Xml;

namespace HarvestIT.Common
{
    /// <summary>
    /// Application settings manager.
    /// </summary>
    public class ConfigManager
    {
        // Configuration file node names.
        private const string SETTING_KEY_NAME = “key”;
        private const string SETTING_VALUE_NAME = “value”;

        public static string GetConfigValue(string SectionName, string KeyName)
        {
            return GetConfigValue(System.Reflection.Assembly.GetEntryAssembly().Location, SectionName, KeyName);
        }

        public static string GetConfigValue(string FileName, string SectionName, string KeyName)
        {
            Configuration config = ConfigurationManager.OpenExeConfiguration(FileName);
            ConfigurationSection section = config.GetSection(SectionName);

            XmlDocument xml = new XmlDocument();
            xml.LoadXml(section.SectionInformation.GetRawXml());

            if (xml.DocumentElement.Attributes[“file”] == null)
            {
                foreach (XmlElement element in xml.ChildNodes[0])
                {
                    if (element.Attributes[SETTING_KEY_NAME].Value == KeyName) return element.Attributes[SETTING_VALUE_NAME].Value;
                };
            }
            else
            {
                return GetConfigValue(System.Reflection.Assembly.GetEntryAssembly().Location.Remove
                    (
                    System.Reflection.Assembly.GetEntryAssembly().Location.LastIndexOf(“\”)) + “\” + xml.DocumentElement.Attributes[“file”].Value.Remove
                        (
                        xml.DocumentElement.Attributes[“file”].Value.LastIndexOf(“.config”)
                        ),
                    SectionName,
                    KeyName);
            }
            return null;
        }

        public static void SetConfigValue(string SectionName, string KeyName, string Value)
        {
            SetConfigValue(System.Reflection.Assembly.GetEntryAssembly().Location , SectionName, KeyName, Value);
        }

        public static void SetConfigValue(string FileName, string SectionName, string KeyName, string Value)
        {
            Configuration config = ConfigurationManager.OpenExeConfiguration(FileName);
            ConfigurationSection section = config.GetSection(SectionName);

            XmlDocument xml = new XmlDocument();
            xml.LoadXml(section.SectionInformation.GetRawXml());

            if (xml.DocumentElement.Attributes[“file”] == null)
                {
                    foreach (XmlElement element in xml.ChildNodes[0])
                    {
                        if (element.Attributes[SETTING_KEY_NAME].Value == KeyName) element.Attributes[SETTING_VALUE_NAME].Value = Value;
                    };

                    section.SectionInformation.SetRawXml(xml.OuterXml);
                    config.Save();
                }
            else
                {
                    SetConfigValue(System.Reflection.Assembly.GetEntryAssembly().Location.Remove
                        (
                        System.Reflection.Assembly.GetEntryAssembly().Location.LastIndexOf(“\”)) + “\” + xml.DocumentElement.Attributes[“file”].Value.Remove
                            (
                            xml.DocumentElement.Attributes[“file”].Value.LastIndexOf(“.config”)
                            ),
                        SectionName,
                        KeyName,
                        Value);
                }
        }
    }
}

Randy Walker's Picture

About Randy Walker

Randy is an entrepreneur, software developer, amateur photographer, speaker and influencer.

Bentonville, Arkansas http://thesaltykorean.com