Creating Custom Configuration Sections in App.config

I recently started a new job as a Software Developer working on extending Sage software functionality. Much of this development is done in Visual C#. Through the projects I have been given, there have been a few concepts I have learnt that...

⚠️
This article is out of date.
.NET Core and .NET 5+ use appsettings.json files which are far more flexible and easy to customize.

I recently started a new job as a Software Developer working on extending Sage software functionality. Much of this development is done in Visual C#. Through the projects I have been given, there have been a few concepts I have learnt that have proven to be very useful. One such concept is custom Configuration Sections in a Visual C# application’s App.config file.

What’s this App.config file even?

An App.config file is an XML file included in various Visual C# applications. This file is used to define settings for your app to function such as connection strings to databases (the most common usage) and any other data related to your application. It is common practice for this file to be configured when an application is installed.

The following is an example of an App.config file that is used by an application to store licence information for the application:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
    </startup>
    <appSettings>
        <add key="licenceKey"
             value="1233-4234-6545-6732-3433"/>
        <add key="licenceOwner"
             value="Ivan Kahl"/>
    </appSettings>
</configuration>

As you can see we store the licence information as simple key value pairs. These key value pairs are stored in a predefined Configuration Section called appSettings. There are various predefined Configuration Sections defined that you can use. These predefined Configuration Sections are quite useful, but you might find some cases where it is easier and neater for you to write your own configuration section rather than use one of the predefined ones.

When would I want to create my own Configuration Section?

Knowing that we can create our own Configuration Sections, it’s important that we don’t get carried away with it. The Configuration Sections provided are quite powerful and there are a lot so I encourage you to explore them first. For example, don’t create a Configuration Section for connection strings when there is already a predefined Configuration Section for connection strings called <connectionStrings>.

You want to use your own Configuration Section when none of the predefined Configuration Sections are able to store your data in a tidy manner. I recently had to write software that could access multiple Sage CRM instances. I needed to store data for these Sage CRM instances in the App.config so that they could be read and used in the application. My first attempt looked as follows:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup>
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
    </startup>
    <appSettings>
        <add key="sageCRMInstance1Name"
             value="AdventureHouseAppliances"/>
        <add key="sageCRMInstance1Server"
             value="192.168.23.45" />
        <add key="sageCRMInstance1InstallationName"
             value="MainCRM"/>
        <add key="sageCRMInstance2Name"
             value="AdventureTechnology"/>
        <add key="sageCRMInstance2Server"
             value="192.168.76.2" />
    </appSettings>
</configuration>

As you can see, this attempt got messy very quickly. While we could argue that it sort of gets the job done, it’s not very user-friendly to set up and (in my case, one of the bigger problems) we cannot iterate through each Sage CRM instance settings intuitively. This is a good example of where a custom Configuration Section might be a better option.

If we use a custom Configuration Section, we could store our Sage CRM instance settings as follows:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
    </startup>
    <sageCRM>
        <instances>
            <add name="AdventureHouseAppliances"
                 server="192.168.23.45"
                 installationName="MainCRM"/>
            <add name="AdventureTechnology"
                 server="192.168.76.2"/>
        </instances>
    </sageCRM>
</configuration>

This is undeniably a lot neater to read and edit. It is very easy for someone who is configuring the application to add, edit and delete Sage CRM instance settings. In order to implement this Configuration Section, we will need to build our own one.

Building a custom Configuration Section

In order to create a custom Configuration Section, we will be creating a few classes to represent all the different elements in the Configuration Section. I will be storing all these classes in folder called SageCRMConfiguration in my Console Application project. Refer to the image below.

We will build this Configuration Section from the bottom. We will start with creating an class that can store the instance settings (the <add> element), then we’ll create a collection that can store our instances (the <instances> element) and then we’ll create the class to manage the Configuration Section (the <sageCRM> element).

Prerequisites

As a little note before we start, please make sure that you reference the System.Configuration assembly in your project. This assembly is needed to build the Configuration Section.

Creating the SageCRMInstanceElement

This class will contain all the data for a single Sage CRM Instance. This is equivalent to the <add> element in the App.config file displayed above.

The class will look as follows:

using System.Configuration;
 
namespace CustomConfigSection.SageCRMConfiguration
{
    public class SageCRMInstanceElement : ConfigurationElement
    {
        // Create a property to store the name of the Sage CRM Instance
        // - The "name" is the name of the XML attribute for the property
        // - The IsKey setting specifies that this field is used to identify
        //   element uniquely
        // - The IsRequired setting specifies that a value is required
        [ConfigurationProperty("name", IsKey = true, IsRequired = true)]
        public string Name
        {
            get
            {
                // Return the value of the 'name' attribute as a string
                return (string)base["name"];
            }
            set
            {
                // Set the value of the 'name' attribute
                base["name"] = value;
            }
        }
 
        // Create a property to store the server of the Sage CRM Instance
        // - The "server" is the name of the XML attribute for the property
        // - The IsRequired setting specifies that a value is required
        [ConfigurationProperty("server", IsRequired = true)]
        public string Server
        {
            get
            {
                // Return the value of the 'server' attribute as a string
                return (string)base["server"];
            }
            set
            {
                // Set the value of the 'server' attribute
                base["server"] = value;
            }
        }
 
        // Create a property to store the installation name of the Sage CRM Instance
        // - The "installationName" is the name of the XML attribute for the property
        // - The IsRequired setting specifies that a value isn't required
        // - The DefaultValue setting specifies a value if none is specified in the XML
        [ConfigurationProperty("installationName", IsRequired = false, DefaultValue = "CRM")]
        public string InstallationName
        {
            get
            {
                return (string)base["installationName"];
            }
            set
            {
                base["installationName"] = value;
            }
        }
    }
}

Creating the SageCRMInstanceCollection

This class will contain the collection of SageCRMInstanceElement items and will correspond to the <instances> element.

The code for this class looks as follows:

using System.Configuration;
 
namespace CustomConfigSection.SageCRMConfiguration
{
    public class SageCRMInstanceCollection : ConfigurationElementCollection
    {
        // Create a property that lets us access an element in the
        // collection with the int index of the element
        public SageCRMInstanceElement this[int index]
        {
            get
            {
                // Gets the SageCRMInstanceElement at the specified
                // index in the collection
                return (SageCRMInstanceElement)BaseGet(index);
            }
            set
            {
                // Check if a SageCRMInstanceElement exists at the
                // specified index and delete it if it does
                if (BaseGet(index) != null)
                    BaseRemoveAt(index);
 
                // Add the new SageCRMInstanceElement at the specified
                // index
                BaseAdd(index, value);
            }
        }
 
        // Create a property that lets us access an element in the
        // colleciton with the name of the element
        public new SageCRMInstanceElement this[string key]
        {
            get
            {
                // Gets the SageCRMInstanceElement where the name
                // matches the string key specified
                return (SageCRMInstanceElement)BaseGet(key);
            }
            set
            {
                // Checks if a SageCRMInstanceElement exists with
                // the specified name and deletes it if it does
                if (BaseGet(key) != null)
                    BaseRemoveAt(BaseIndexOf(BaseGet(key)));
 
                // Adds the new SageCRMInstanceElement
                BaseAdd(value);
            }
        }
 
        // Method that must be overriden to create a new element
        // that can be stored in the collection
        protected override ConfigurationElement CreateNewElement()
        {
            return new SageCRMInstanceElement();
        }
 
        // Method that must be overriden to get the key of a
        // specified element
        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((SageCRMInstanceElement)element).Name;
        }
    }
}

Creating the SageCRMConfig

This class will be used to configure the Configuration Section in the App.config file. This will correspond to the <sageCRM> element.

The code for the class is as follows:

using System.Configuration;
 
namespace CustomConfigSection.SageCRMConfiguration
{
    public class SageCRMConfig : ConfigurationSection
    {
        // Create a property that lets us access the collection
        // of SageCRMInstanceElements
 
        // Specify the name of the element used for the property
        [ConfigurationProperty("instances")]
        // Specify the type of elements found in the collection
        [ConfigurationCollection(typeof(SageCRMInstanceCollection))]
        public SageCRMInstanceCollection SageCRMInstances
        {
            get
            {
                // Get the collection and parse it
                return (SageCRMInstanceCollection)this["instances"];
            }
        }
    }
}

Configure the App.config

The last step is to reference our custom Configuration Section in the App.config file so that it knows how to use it. This is done by adding the following to the App.config file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <!-- Add your new Configuration Section here -->
        <section name="sageCRM"
                 type="CustomConfigSection.SageCRMConfiguration.SageCRMConfig, CustomConfigSection"/>
    </configSections>
    ...
</configuration>

Note that the type attribute is in the format MyAssembly.Namespace.To.SageCRMConfig, MyAssembly. If you don’t specify it in this format, it won’t work!

UPDATE: Thanks to My Star in the comments for pointing out that the <configSections> tag needs to be the first child of the root XML document.

Using the custom Configuration Section

We have now built our custom Configuration Section to store our Sage CRM Instance settings. Now we can use it to store instances in the App.config file and read them from code.

Storing the Sage CRM Instance settings in App.config

We can now store our Sage CRM Instance settings in the App.config file as follows:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <section name="sageCRM"
                 type="CustomConfigSection.SageCRMConfiguration.SageCRMConfig, CustomConfigSection"/>
    </configSections>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
    </startup>
    <sageCRM>
        <instances>
            <add name="AdventureHouseAppliances"
                 server="192.168.23.45"
                 installationName="MainCRM"/>
            <add name="AdventureTechnology"
                 server="192.168.76.2"/>
        </instances>
    </sageCRM>
</configuration>

Reading the Sage CRM Instance settings from code

Now that we have added some Sage CRM Instance settings to our App.config, we can now access them from code. In this example, we will loop through each instance and output its attributes.

using CustomConfigSection.SageCRMConfiguration;
using System;
using System.Configuration;
 
namespace CustomConfigSection
{
    class Program
    {
        static void Main(string[] args)
        {
            // Get the custom Configuration Section using its name
            var sageConfig = (SageCRMConfig)ConfigurationManager.GetSection("sageCRM");
 
            // Loop through each instance in the SageCRMInstanceCollection
            foreach (SageCRMInstanceElement instance in sageConfig.SageCRMInstances)
            {
                // Write the instance information to the Console
                Console.WriteLine("{0} {1} {2}",
                    instance.Name,
                    instance.Server,
                    instance.InstallationName);
            }
 
            // Prevent the Console from closing immediately afterwards
            Console.ReadKey();
        }
    }
}

This code outputs the following:

AdventureHouseAppliances 192.168.23.45 MainCRM
AdventureTechnology 192.168.76.2 CRM

This Configuration Section made it incredibly easy to access data from the App.config file. Also, notice how the default Installation Name was used for our second instance because we never specified one in our App.config. This is a benefit we get from using our own Configuration Section.

Conclusion

Hopefully this guide has proven to be helpful to you. Please leave a comment if you have any questions or anything you would like to say. Your input is appreciated.

And remember… with great power comes great responsibility. Use custom Configuration Sections wisely!