Reading Custom Configuration Sections in .NET 2.0

Published 22 March 07 09:35 AM | john 

I just spent an annoying amount of time figuring out how to do this because sadly our documentation is so bad.  So I'm going to blog it quickly so that I at least have a record of what I did and hopefully someone else will find it useful.  Thanks go to Tim Murphy for providing about the only decent blog on this subject.

First the problem.  Let's say that you want to create a custom section called mySection that contains an array of fishes elements.  Here's what the .config file might look like:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="mySection" type="MyNamespace.MySection, MyCustomConfig, Version=1.0.0.0, Culture=neutral, PublicKeyToken=0e9a5ab9342fd91c" />
  </configSections>
  <mySection>
    <fishes>
      <add name="angel" />
      <add name="barracuda" />
    </fishes>
  </mySection>
</configuration>

First thing to note is that you'll have to create an assembly containing your custom configuration section code.  Second thing to note is that it should ideally be strongly named.  Although that's not a requirement it's a good idea.  System.Configuration is the namespace with all the configuration file reading code.  The main configuration elements that you are going to define are ConfigurationElement, ConfigurationElementCollection and ConfigurationSection.

One key comprehension point for me is that the .NET configuration reader is basically trying to read XML elements and attributes in the .config file into a big tree of nested property collections.  So your custom configuration code is basically a big marshaller to and from the .config XML to these property collections.  Knowing this makes the code below make a bit more sense instead of just "magic stuff that works".

Ignore the comments in MSDN that talk about a non-attributed way to work with the System.Configuration classes.  Even though it's the approach that the CLR uses for many of it's section readers, the attributed class mechanism is much simpler to work with and I think completely supercedes the non-attributed mechanism. 

One thing that I screwed up initially was not realizing that I had to have root section before I could define configuration elements, collections, etc..  In other words the mySection element is needed to anchor the fishes element collection, even though it looks redundant.  If I defined other elements, birds, plants, etc.. then the bracketing mySection element wouldn't look so unnecessary.

So, here's the code for the custom section reader:

using System;
using System.Configuration;
using System.Xml;
using System.Reflection;

[assembly:AssemblyVersion("1.0.0.0")]

namespace MyNamespace
{
    public class FishElement : ConfigurationElement
    {
        public FishElement()
        {
        }
        
        [ConfigurationProperty("name", IsRequired = true, DefaultValue="", IsKey = true)]
        public string Name
        {
            get
            {
                return (string)this["name"];
            }
            set
            {
                this["name"] = value;
            }
        }
    }

    [ConfigurationCollection(typeof(FishElement))]
    public class FishElementCollection : ConfigurationElementCollection
    {
        protected override ConfigurationElement CreateNewElement()
        {
            return new FishElement();
        }

        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((FishElement)(element)).Name;
        }
        
        public void Add(FishElement element)
        {
            this.BaseAdd(element);
        }
        
        public void Remove(string key)
        {
            this.BaseRemove(key);
        }
        
        public void Clear()
        {
            this.BaseClear();
        }
        
        public FishElement this[int index]
        {
            get 
            {
                return (FishElement)this.BaseGet(index);
            }
        }

        protected override bool OnDeserializeUnrecognizedElement(string elementName, XmlReader reader)
        {
            return base.OnDeserializeUnrecognizedElement(elementName, reader);
        }
    }

    public class MySection : ConfigurationSection
    {
        [ConfigurationProperty("fishes", IsDefaultCollection = true)]
        public FishElementCollection Fishes
        {
            get
            {
                return (FishElementCollection)(base["fishes"]);
            }
        }

        protected override bool OnDeserializeUnrecognizedElement(string elementName, XmlReader reader)
        {
            // This is a useful method to break on in the debugger because it lets you know what the configuration 
            // reader is up to.
            return base.OnDeserializeUnrecognizedElement(elementName, reader);
        }
    }
}

Here's how to build the code:

csc /target:library /keyfile:CustomConfig.snk MyCustomConfig.cs
csc Program.cs /r:MyCustomConfig.dll

You can generate your own .snk file with sn.exe -k.  And finally, here's some code that uses the custom section reader to read in the custom section given above:

using System;
using System.Configuration;

namespace MyNamespace
{
    public class Program
    {
        public static void Main()
        {
            MySection section;

            Console.WriteLine("Contents of .config section <mySection>:");

            try
            {
                section = (MySection)ConfigurationManager.OpenExeConfiguration(
                    ConfigurationUserLevel.None).GetSection("mySection");
            }
            catch (ConfigurationException e)
            {
                Console.WriteLine("Problem reading configuration section : {0}", e.Message);
                return;
            }

            if (section == null)
            {
                Console.WriteLine("Section not found");
                return;
            }

            for (int i = 0; i < section.Fishes.Count; i++)
            {
                Console.WriteLine(section.Fishes[i].Name);
            }
        }
    }
}

You'll discover that you can leave out the strong version name in the section name= element and everything still works.  Useful during testing.  The documentation doesn't mention that either. 

Filed under: , , ,

Comments

# The Director of Random Technologies said on July 23, 2007 4:01 PM:

While I haven't been posting much here, I have been posting pretty regularly over on my main development

# Noticias externas said on July 23, 2007 5:02 PM:

While I haven&#39;t been posting much here, I have been posting pretty regularly over on my main development

Anonymous comments are disabled