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:

XML:
  1. <configSections>
  2.     <sectionGroup name="brh.web">
  3.        <section
  4.          name="redirectOldUrls"
  5.          type="Brh.Web.RedirectConfigurationHandler, Brh.Web.Utility"
  6.        />
  7.    </sectionGroup>
  8.   </configSections>
  9.  
  10.   <brh.web>
  11.     <redirectOldUrls>
  12.       <redirect filePattern="tools.aspx" url="~/tools/" />
  13.       <redirect filePattern="strategy.aspx" url="~/strategy/" />
  14.         <redirect filePattern="military_people.aspx" url="~/people/" />
  15.         <redirect filePattern="history.aspx" url="~/history/" />
  16.         <redirect filePattern="teacher.aspx" url="~/teacher/" />
  17.     </redirectOldUrls>
  18.   </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:

C#:
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.Configuration;
  5.  
  6. namespace Brh.Web
  7. {
  8.     class RedirectConfigurationHandler : ConfigurationSection
  9.     {
  10.         [ConfigurationProperty("", IsDefaultCollection=true, IsKey=false, IsRequired=true)]
  11.         public RedirectCollection Redirects
  12.         {
  13.             get
  14.             {
  15.                 return base[""] as RedirectCollection;
  16.             }
  17.  
  18.             set
  19.             {
  20.                 base[""] = value;
  21.             }
  22.         }
  23.     }
  24.  
  25.     class RedirectCollection : ConfigurationElementCollection
  26.     {
  27.  
  28.         protected override ConfigurationElement CreateNewElement()
  29.         {
  30.             return new RedirectElement();
  31.         }
  32.        
  33.         protected override object GetElementKey(ConfigurationElement element)
  34.         {
  35.             return ((RedirectElement)element).FilePattern;
  36.         }
  37.        
  38.         protected override string ElementName
  39.         {
  40.             get
  41.             {
  42.                 return "redirect";
  43.             }
  44.         }
  45.  
  46.         protected override bool IsElementName(string elementName)
  47.         {
  48.             return !String.IsNullOrEmpty(elementName) && elementName == "redirect";
  49.         }
  50.  
  51.         public override ConfigurationElementCollectionType CollectionType
  52.         {
  53.             get
  54.             {
  55.                 return ConfigurationElementCollectionType.BasicMap;
  56.             }
  57.         }
  58.  
  59.        
  60.         public RedirectElement this[int index] {
  61.             get
  62.             {
  63.                 return base.BaseGet(index) as RedirectElement;
  64.             }
  65.         }
  66.     }
  67.  
  68.     class RedirectElement : ConfigurationElement
  69.     {
  70.         [ConfigurationProperty("filePattern", IsRequired=true, IsKey=true)]
  71.         public string FilePattern
  72.         {
  73.             get { return base["filePattern"] as string; }
  74.             set { base["filePattern"] = value; }
  75.         }
  76.  
  77.         [ConfigurationProperty("url", IsRequired=true, IsKey=false)]
  78.         public string RedirectUrl
  79.         {
  80.             get { return base["url"] as string; }
  81.             set { base["url"] = value; }
  82.         }
  83.     }
  84. }

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:

C#:
  1. 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.

Tags: ,

View blog reactions

7 Responses to “Creating nested custom configuration sections in ASP.NET 2.0”

  1. counterstalker Says:

    Thanks for sharing this - I had the exact same problem and spent all night yesterday trying to resolve it.

    Five minutes adapting your example for my needs and I was done.

    Just gotta love blogs :D

  2. Arjan Says:

    Thnx man, really nice!!

  3. Rolf Hansen Says:

    Thanks this worked great!

    I did modify it a bit though and added access to a collection item with a key string value.

  4. Nicholas Blumhardt Says:

    Thanks Ben, worked perfectly.

  5. Maicom Kusma Says:

    Great! Tnx!!!

  6. Jens Mikkelsen Says:

    This article is spot on and helped me alot. Thanks!

  7. Dave Duchene Says:

    THANK YOU.

Leave a Reply