Generic config section handlers

It’s been a while since I had to cre­ate a con­fig sec­tion han­dler and my god did I for­get how cum­ber­some a process it is!

Instead of doing all these work to make a bespoke con­fig sec­tion han­dler every time you want to parse some data out of the app.config file, wouldn’t it be nice to have a more gener­ic, reusable com­po­nent to do all the work for you? Well, turns out it wasn’t all that hard to make one either!

Broad­ly speak­ing, you usu­al­ly want to parse either an object, or a col­lec­tion of objects out of the con­fig file, and they required slight­ly dif­fer­ent han­dling so I end­ed up writ­ing one for each.

Single Object

The sin­gle object case is fair­ly easy, all you need is an XmlSe­ri­al­iz­er for dese­ri­al­iz­ing the XML node:

   1: public sealed class GenericConfigSectionHandler<T> : IConfigurationSectionHandler

   2: {

   3:     public object Create(object parent, object configContext, XmlNode section)

   4:     {

   5:         var xmlSerializer = new XmlSerializer(typeof(T));

   6:         var xmlNodeReader = new XmlNodeReader(section);

   7:         return xmlSerializer.Deserialize(xmlNodeReader);

   8:     }

   9: }

Assum­ing you have a sim­ple class like this:

   1: [XmlRoot("MyClass")]

   2: public class MyClass

   3: {

   4:     public string Name { get; set; }

   5:

   6:     public int Age { get; set; }

   7: }

To parse it out of your app.config file, you need some­thing like this in the con­fig file:

   1: <configuration>

   2:   <configSections>

   3:     <section name="MyClass"

   4:              type="ConsoleApplication.GenericConfigSectionHandler`1[[ConsoleApplication.MyClass, ConsoleApplication]], ConsoleApplication"/>

   5:   </configSections>

   6:

   7:   <MyClass>

   8:     <Name>Yan</Name>

   9:     <Age>29</Age>

  10:   </MyClass>

  11: <configuration>

And your code will be the same as before:

   1: var myObj = (MyClass)ConfigurationManager.GetSection("MyClass");

Collections

Col­lec­tions usu­al­ly rep­re­sent a whole new lev­el of pain because to get an array of objects out of the con­fig file you have to first cre­ate a wrap­per object to hold the array. You then need to set up your con­fig sec­tion to parse the wrap­per object instead of the array itself, just an addi­tion­al hoop you have to jump through to get some­thing sim­ple done…

Well, with this Gener­ic­Col­lec­tion­Con­fig­Sec­tion­Han­dler class hope­ful­ly you won’t ever have to do that again!

   1: public sealed class GenericCollectionConfigSectionHandler<T> : IConfigurationSectionHandler

   2: {

   3:     static GenericCollectionConfigSectionHandler()

   4:     {

   5:         // get the XmlRootAttribute element on the type

   6:         var type = typeof(T);

   7:         var xmlRootAttributes =

   8:             type.GetCustomAttributes(typeof(XmlRootAttribute), false)

   9:                 .OfType<XmlRootAttribute>();

  10:

  11:         // if an XmlRootAttribute is found then use its ElementName property as the

  12:         // root element name for the type T, otherwise, use the name of the type T

  13:         // as the default root element name

  14:         RootElementName =

  15:             !xmlRootAttributes.Any()

  16:                 ? type.Name

  17:                 : xmlRootAttributes.First().ElementName;

  18:     }

  19:

  20:     private static string RootElementName { get; set; }

  21:

  22:     public object Create(object parent, object configContext, XmlNode section)

  23:     {

  24:         var xmlSerializer = new XmlSerializer(typeof(T));

  25:         var xmlNodeReader = new XmlNodeReader(section);

  26:         var xdoc = XDocument.Load(xmlNodeReader);

  27:         var items =

  28:             xdoc.Descendants(RootElementName)

  29:                 .Select(e => (T)xmlSerializer.Deserialize(e.CreateReader()));

  30:         return items.ToArray();

  31:     }

  32: }

As you can see, this class is pret­ty sim­ple, to use it your con­fig file ought to look a lit­tle like this:

   1: <configuration>

   2:   <configSections>

   3:     <section name="MyClasses"

   4:              type="ConsoleApplication.GenericCollectionConfigSectionHandler`1[[ConsoleApplication.MyClass, ConsoleApplication]], ConsoleApplication"/>

   5:   </configSections>

   6:

   7:   <MyClasses>

   8:     <MyClass>

   9:       <Name>Yan</Name>

  10:       <Age>29</Age>

  11:     </MyClass>

  12:     <MyClass>

  13:       <Name>Yinan</Name>

  14:       <Age>29</Age>

  15:     </MyClass>

  16:   </MyClasses>

  17: </configuration>

So you see, no wrap­per class required and you get an array of MyClass instances back:

   1: var myObjs = (MyClass[])ConfigurationManager.GetSection("MyClasses");