Creating nested custom configuration sections in ASP.NET 2.0

,

This weekend I decided to go through the hodgepodge of common code that’s shared between a lot of my ASP.NET websites and refactor it a bit. I’d only just learned about the magic of HttpModules and HttpHandlers, and I immediately saw a lot of canidates in my copy-paste code and global.asax handlers where a HttpModule would be a better solution. One of these was the code I was using to redirect old pages to new pages whenever I moved them. For example, at some point I had moved http://www.numbera.com/rome/tools.aspx to http://www.numbera.com/rome/tools/, and I wanted anyone who visited the old URL to get redirected to the new one. Previously, I just had some code in global.asax that hooked Application_OnError, checked to see if it was an HttpException (a 404 file not found, specifically), and then redirected if it knew where the file really was. Pretty simple, but not very general. So I broke it out into an HttpModule that basically did the same thing, but no longer required me to cut and paste code into my global.asax. However, one improvement I wanted to make was to allow for configuration through my web.config file, instead of having to hardcode an if/else tree for each redirect. I basically wanted to have a section in my web.config like this:

<configSections>
    <sectionGroup name="brh.web">
     <section 
       name="redirectOldUrls" 
       type="Brh.Web.RedirectConfigurationHandler, Brh.Web.Utility"
     />
 </sectionGroup>
</configSections>

<brh.web>
  <redirectOldUrls>
      <redirect filePattern="tools.aspx" url="~/tools/" />
    <redirect filePattern="strategy.aspx" url="~/strategy/" />
    <redirect filePattern="military_people.aspx" url="~/people/" />
    <redirect filePattern="history.aspx" url="~/history/" />
    <redirect filePattern="teacher.aspx" url="~/teacher/" />
  </redirectOldUrls>
</brh.web>

To do this, I needed to create a custom configuration handler. There’s plenty of documentation out there on how to create a single-tag configuration handler using the new ConfigurationSectionHandler class, and all of the sites I could find talked about how much easier it was to do things the new .NET 2.0 way instead of using IConfigurationSectionHandler. However, nothing I could find would tell me how to make a config section with sub-elements. It’s certainly easy to decorate a property with the ConfigurationProperty attribute, but setting up the collection of subelements is significantly harder. Finally, after chancing upon a forum post, I was able to hack together something that worked:

using System;
using System.Collections.Generic;
using System.Text;
using System.Configuration;

namespace Brh.Web
{
    class RedirectConfigurationHandler : ConfigurationSection
    {
        [ConfigurationProperty("", IsDefaultCollection=true, IsKey=false, IsRequired=true)]
        public RedirectCollection Redirects
        {
            get
            {
                return base[""] as RedirectCollection;
            }

            set
            {
                base[""] = value;
            }
        }
    }

    class RedirectCollection : ConfigurationElementCollection
    {

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

        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((RedirectElement)element).FilePattern;
        }

        protected override string ElementName
        {
            get
            {
                return "redirect";
            }
        }

        protected override bool IsElementName(string elementName)
        {
            return !String.IsNullOrEmpty(elementName) && elementName == "redirect";
        }

        public override ConfigurationElementCollectionType CollectionType
        {
            get
            {
                return ConfigurationElementCollectionType.BasicMap;
            }
        }


        public RedirectElement this[int index] {
            get
            {
                return base.BaseGet(index) as RedirectElement;
            }
        }
    }

    class RedirectElement : ConfigurationElement
    {
        [ConfigurationProperty("filePattern", IsRequired=true, IsKey=true)]
        public string FilePattern
        {
            get { return base["filePattern"] as string; }
            set { base["filePattern"] = value; }
        }

        [ConfigurationProperty("url", IsRequired=true, IsKey=false)]
        public string RedirectUrl
        {
            get { return base["url"] as string; }
            set { base["url"] = value; }
        }
    }
}

The important parts here are that I overrode the CollectionType on RedirectCollection, which allows me to use a simple mapping instead of the more complex add/remove/clear pattern that the new Configuration API selects by default. Also, I overrode the parts about the element name, so that it would recognize a series of “redirect” tags as my collection. The last bit that hung me up for a long time was that my ConfigurationElement, RedirectElement, needs to store its data in the base property collection. I had been storing them as properties, thinking that the configuration framework would populate them via reflection, but all that got me was a “Invalid key value” error, since the collection was exposing RedirectElement.FilePattern as the “key” (whatever that means) and it was null. I still don’t really understand all the craziness about this new configuration system, but at least I got it working. I wish there was better documentation out there (or at least better error messages). In general I think this is way too complex a way to go about handling configuration - I’ll stick with simply serializing/unserializing a custom settings object in my Windows apps.

One last bit: to use this in your app, simply do this:

RedirectConfigurationHandler config = (RedirectConfigurationHandler)System.Configuration.ConfigurationManager.GetSection("brh.web/redirectOldUrls");

Now you have a strongly typed config object that you can use to read your settings.

I'm Benjamin Hollis, a software developer in Seattle. Check out my website.