Categories: development

Having a nested Configuration Section in web.config

I'm currently working on an ASP.net Application for myself, and I was thinking about Configuration. I wanted to use web.config as this is the proper way, but I was unsure what the best way is. I did not want to use appSettings as I feel my own "block" is just more tidy. Here is how I want the actual configuration to look like:
<myApp injectModule="MyApp.MyAppTestNinjectModule">
  <localeSettings longDateFormat="MM/dd/yyyy HH:mm:ss" />
</myApp>
So I have "myApp" as my main element, with an attribute "injectModule" and a child element "localeSettings". Child Elements allow me to group my settings even more, as I could have in theory dozens of settings, and having them all as attributes to the main myApp-element is messy.Now, in order to use the myApp element, I need to tell .net about it in the <configSections> section of the web.config. There are two options: section and sectionGroup. I thought that sectionGroup is the correct one, but sectionGroups cannot have attributes, only child Elements. So they are just containers. On the other hand, sections can have attributes and child elements. I don't exactly know why one would use sectionGroup, but I haven't investigated on this too much as it was unsuitable.So we need to create a section by adding this to the configSections xml element:
<section name="myApp" type="MyApp.MyAppConfigurationSection"/>
What does this do? It tells ASP.net that we will have an xml Element called myApp, and that this is handled though the MyApp.MyAppConfigurationSection Class. How does this class look like?
public class MyAppConfigurationSection : ConfigurationSection
{
    private static ConfigurationPropertyCollection _properties;
    private static ConfigurationProperty _propInjectModule;

    static MyAppConfigurationSection()
    {
        _propInjectModule = new ConfigurationProperty("injectModule", typeof (string),
                                                      "MyApp.MyAppNinjectModule",
                                                      ConfigurationPropertyOptions.None);
        _properties = new ConfigurationPropertyCollection { _propInjectModule };
    }

    protected override ConfigurationPropertyCollection Properties
    {
        get
        {
            return _properties;
        }
    }

    public string InjectModule
    {
        get { return this[_propInjectModule] as string; }
        set { this[_propInjectModule] = value; }
    }
}
Whoa, scary... But let's look at this piece by piece. First of all, your class inherits from ConfigurationSection. This is the base class in the .net Framework to represent a section.We start by defining a ConfigurationProperty named _propInjectModule and initializing it in the static constructor. The four parameters to the new ConfigurationProperty constructor are:
  1. The name of the attribute/property as it appears in the web.config later
  2. The Type of the property
  3. The default value - this is important if the parameter is not set in the web.config
  4. Options for this Attribute. I have not checked what IsKey and IsDefaultCollection do, but if IsRequired is set then it has to be set in the web.config
So in a nutshell, _propInjectModule is a string that defaults to MyApp.MyAppNinjectModule and is represented in the web.config as injectModule.The ConfigurationPropertyCollection is needed by .net for some internal plumbing I guess - I haven't checked on it's exact purpose.Finally, we have the public string InjectModule Property. This is used to access the actual value of the Property. If it's not set in the web.config, the Default value is returned instead. As this is using the Indexer, I'm guessing that this is why the _properties is important.So, how do we use it? There are multiple options, but I found having a static function most useful:
public static MyAppConfigurationSection GetMyAppConfig()
{
    var result = WebConfigurationManager.GetWebApplicationSection("myApp") as MyAppConfigurationSection;
    return result ?? new MyAppConfigurationSection();
}
This will try to get the myApp Section of the web.config. If this returns null (i.e. if there is none), then it will just initialize a new instance of the class so that we get it in any case. This works because we have default values for all Properties (Well, there is only one for now). If you want to have required Properties, you possibly need to do something harsh on that.Okay, so try accessing MyAppConfigurationSection.GetMyAppConfig().InjectModule and you should get either the Default Value of "MyApp.MyAppNinjectModule" or whatever you have specified in the <myApp injectModule="MyApp.MyAppTestNinjectModule"> element of your web.config.So far, so good. But what about the nested localeSettings element? If you just add it to the web.config, you should get an error message explaining that it couldn't parse this element. Let's wire it up.If you want child elements to a ConfigurationSection, you have to use a ConfigurationElement. I do not exactly know what the real difference between a Section and an Element is and why we need three different layers of nesting (sectionGroup - section - element), but I'm sure the Framework designers had something in mind here.Let us first implement the .net Class for the ConfigurationElement:
public class MyAppLocaleSettingsElement : ConfigurationElement
{
    private readonly static ConfigurationPropertyCollection _properties;
    private readonly static ConfigurationProperty _propLongDateFormat;

    protected override ConfigurationPropertyCollection Properties
    {
        get
        {
            return _properties;
        }
    }


    static MyAppLocaleSettingsElement()
    {
        _propLongDateFormat = new ConfigurationProperty("longDateFormat", typeof (string),
                                                        "ddd, MMM dd, yyyy HH:mm",
                                                        ConfigurationPropertyOptions.None);
        _properties = new ConfigurationPropertyCollection { _propLongDateFormat };
    }

    public string LongDateFormat
    {
        get { return this[_propLongDateFormat] as string; }
        set { this[_propLongDateFormat] = value; }
    }
}
As you see, this works exactly the same as a ConfigurationSection: We have a string property with a Default Value and a public Property.What we need to do now is to wire it into the MyAppConfigurationSection class. Here are the changes to the class:
public class MyAppConfigurationSection : ConfigurationSection
{
    private readonly static ConfigurationProperty _propLocaleSettings;
    static MyAppConfigurationSection()
    {
        _propLocaleSettings = new ConfigurationProperty("localeSettings", typeof(MyAppLocaleSettingsElement),
                                                        new MyAppLocaleSettingsElement(),
                                                        ConfigurationPropertyOptions.None);
        _properties = new ConfigurationPropertyCollection { _propInjectModule, _propLocaleSettings };
    }
    public MyAppLocaleSettingsElement LocaleSettings
    {
        get { return this[_propLocaleSettings] as MyAppLocaleSettingsElement; }
    }
}
We created a new property that is represented through the "localeSettings" element in the xml. For the default value, I am returning a new instance - again, this works because I specified default Values. Finally, I have a new Property LocaleSettings which exposes the child element through the getter.To access this, we can do MyAppConfigurationSection.GetMyAppConfig().LocaleSettings.LongDateFormat and either retrieve the default value or the one specified in the web.config.Here is a recommendation though: I would recommend adding a separate Settings class that your application uses. This class sits between the MyAppConfigurationSection class and your application (it serves as a Facade). Why? To make sure that you can change your configuration structure without making too many breaking changes. Maybe I decide that "injectModule" shouldn't be an attribute to the myApp element and instead I want to add a child Element? Or maybe I don't want to use web.config at all and instead use a SQL Server database or a Web Service? Or maybe all of a sudden I decided that injectModule is a required Parameter, which means I now need to handle the case where the user hasn't added a myApp element to his web.config?Here is one example of Settings class:
public static class MyAppSettings
{
    private static readonly MyAppConfigurationSection Config;

    static MyAppSettings()
    {
        Config = MyAppConfigurationSection.GetMyAppConfig();
    }

    public static class LocaleSettings
    {
        public static string LongDateFormat
        {
            get
            {
                return Config.LocaleSettings.LongDateFormat;
            }
        }
    }

    public static string InjectModule
    {
        get { return Config.InjectModule; }
    }
}
I'm using nested classes to group it, but I can now change the MyAppConfigurationSection class (or completely remove it) and only need to do changes in the MyAppSettings Facade-class without affecting the rest of my application.If you really want to dig deep into this, check out these three links (thanks to marc_s):