C# .NET Custom ConfigurationSection, ConfigurationElementCollection, and ConfigurationElement

The built in configuration classes for .NET are powerful and incredibly useful but can be quite confusing due to their flexibility. This is a simple example of a custom ConfigurationSection with custom element names and nested ConfigurationElementCollection objects.

FIRST: Add a reference to System.Configuration in your project and make sure to add “using System.Configuration;” to the top of your class files.

This is the App.Config file the example with comments to explain each important line.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <!-- 
  configSections must be immediately after <configuration>
  type = the full namespace to your custom configuration section then a comma then the file name of the assembly it is in without the extension (no .exe or .dll)
  The default namespace for this example is 'ConfigTesting' and the assembly name is 'ConfigTesting.exe' or "DefaultNamespace.ClassName, AssemblyName"
  which gives ConfigTesting.MyConfigSection, ConfigTesting
  -->
  <configSections>
    <section name="MyConfig" type="ConfigTesting.MyPetsConfigSection, ConfigTesting"/>
  </configSections>
  
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>

  <!-- The name of the section is whatever is in the 'name' attribute in the above section definition -->
  <MyConfig>
    <!-- The word Pet replaces the standard 'add' keyword to add an element to the collection -->
    <Pet name="FirstElement" personality="FirstPersonality">
      <!-- 
      The 'Pet' element contains a default collection in the same way as the configuration section. 
      The Toy collection is the one accessed without any extra tags around it.
      Here the 'Toy' word replaces the standard 'add' keyword
      -->
      <Toy name="Toy1" description="desc1" />
      <!-- 
      This is an additional collection that must have extra tags around it to show .NET that it is a collection.
      In this case the collection is called 'Food'
      -->
      <Food>
        <!-- The word 'Product' replaces the standard 'add' keyword -->
        <Product name="Collards Dry Food" source="Kennelgate" />
      </Food>
    </Pet>
    
    <!-- Second 'Pet' element without the per-line comments -->
    <Pet name="SecondElement" personality="SecondPersonality">
      <Toy name="Toy2" description="desc2" />
      <Toy name="Toy3" description="desc3" />
      <Food>
        <Product name="Lamb Liver" source="Butchers" />
        <Product name="Carrots" source="Garden" />
        <Product name="Cashews" source="Tesco" />
      </Food>
    </Pet>
    
  </MyConfig>
</configuration>

Defining the section itself is relatively easy.

using System.Configuration;

namespace ConfigTesting
{
    public class MyPetsConfigSection : ConfigurationSection
    {
        //This makes the collection the default one that is accessed.
        //Look at the App.config and see that the collection's elements are immediately within the section instead of 
        //being enclosed by another layer of tags
        [ConfigurationProperty("", IsDefaultCollection = true, IsRequired = true)]
        public MyPetConfigCollection Pets
        {
            get { return base[""] as MyPetConfigCollection; }
            set { base[""] = value; }
        }
    }
}

This is the class referenced in the configSection tag in App.Config. You can see that it has one property which is of type MyPetConfigCollection.

using System.Configuration;

namespace ConfigTesting
{
    public class MyPetConfigCollection : ConfigurationElementCollection
    {
        public override ConfigurationElementCollectionType CollectionType => ConfigurationElementCollectionType.BasicMap;
        protected override string ElementName => "Pet";

        protected override ConfigurationElement CreateNewElement()
        {
            return new MyPetConfigElement();
        }

        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((MyPetConfigElement)element).Name;
        }
    }
}

The MyPetConfigCollection is a simple class that is mostly generated for you. The CreateNewElement and GetElementKey methods are compulsory and Visual Studio offers to generate them for you but you have to fill in the content. CreateNewElement should simply return a new object of whatever type the collection holds while GetElementKey should return the key of the given object (it’s like a primary key, if you’re familiar with databases) which is defined in the element class.

NOTE: Overriding ElementName is how you replace the default ‘add’ keyword with your own string but you must also override CollectionType.

 

using System.Configuration;

namespace ConfigTesting
{
    public class MyPetConfigElement : ConfigurationElement
    {
        //This is the key for the element, it's how to find this particular one in the
        //collection
        [ConfigurationProperty("name", IsKey = true, IsRequired = true)]
        public string Name
        {
            get { return base["name"] as string; }
            set { base["name"] = value; }
        }

        [ConfigurationProperty("personality", IsRequired = true)]
        public string Personality
        {
            get { return base["personality"] as string; }
            set { base["personality"] = value; }
        }

        //This is the default collection for this object and can be accessed without looking for extra tags
        [ConfigurationProperty("", IsDefaultCollection = true, IsRequired = true)]
        public PetToyCollection Toys
        {
            get { return base[""] as PetToyCollection; }
            set { base[""] = value; }
        }

        //This is another collection and must be accessed by using its enclosing tag. In this case <Food>
        [ConfigurationProperty("Food", IsRequired = false)]
        public PetFoodCollection Food
        {
            get { return base["Food"] as PetFoodCollection; }
            set { base["Food"] = value; }
        }
    }
}

This element contains two collections, one is the default collection and the other is specifically named Food. It’s important to note that the in-code name of the property does not need to match the tags/attributes in App.Config. The code names are used in code, the ConfigurationProperty names are used in App.Config.

using System.Configuration;

namespace ConfigTesting
{
    public class PetToyCollection : ConfigurationElementCollection
    {

        public override ConfigurationElementCollectionType CollectionType => ConfigurationElementCollectionType.BasicMap;
        protected override string ElementName => "Toy";

        protected override ConfigurationElement CreateNewElement()
        {
            return new ToyElement();
        }

        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((ToyElement)element).Name;
        }
    }
}

This is the toys collection, which is the default collection (you don’t have to have a default).

using System.Configuration;

namespace ConfigTesting
{
    public class ToyElement : ConfigurationElement
    {
        [ConfigurationProperty("name", IsKey = true, IsRequired = true)]
        public string Name
        {
            get { return base["name"] as string; }
            set { base["name"] = value; }
        }

        [ConfigurationProperty("description", IsRequired = true)]
        public string Description
        {
            get { return base["description"] as string; }
            set { base["description"] = value; }
        }
    }
}

The ToyElement does not contain any collections but it could if you wanted to add some here.

 

The below two blocks are the Food collection and elements.

using System.Configuration;

namespace ConfigTesting
{
    public class PetFoodCollection : ConfigurationElementCollection
    {

        public override ConfigurationElementCollectionType CollectionType => ConfigurationElementCollectionType.BasicMap;
        protected override string ElementName => "Product";

        protected override ConfigurationElement CreateNewElement()
        {
            return new PetFoodElement();
        }

        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((PetFoodElement)element).Name;
        }
    }
}

 

using System.Configuration;

namespace ConfigTesting
{
    public class PetFoodElement : ConfigurationElement
    {
        //Name of pet food product
        [ConfigurationProperty("name", IsKey = true, IsRequired = true)]
        public string Name
        {
            get { return base["name"] as string; }
            set { base["name"] = value; }
        }

        //Shop to buy the pet food from
        [ConfigurationProperty("source", IsRequired = true)]
        public string Source
        {
            get { return base["source"] as string; }
            set { base["source"] = value; }
        }
    }
}

Finally, here is the test I used in a Windows Forms application to make sure each element was usable. This iterates over each element and I had a breakpoint to pause so I could hover over it to read the contents.

private void button1_Click(object sender, EventArgs e)
        {
            MyPetsConfigSection section = ConfigurationManager.GetSection("MyConfig") as MyPetsConfigSection;

            var c = section.Pets.Count;
            foreach (MyPetConfigElement i in section.Pets)
            {
                StringBuilder sb = new StringBuilder();
                sb.AppendLine($"{i.Name} has {i.Toys.Count} toy(s)");
                if (i.Food != null)
                    sb.AppendLine($"Foods: {i.Food.Count}");

                MessageBox.Show(sb.ToString(), i.Personality);
            }
        }

 

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Proudly powered by WordPress | Theme: Baskerville 2 by Anders Noren.

Up ↑

%d bloggers like this: