CMAB-Why XmlSerializableHashtable in the Configuration Management Application Block does not support objects

The Configuration Management Application Block, CMAB, is a great tool for managing configuration data. If you wish to manage data outside of app.config or web.config, and IMHO it is the tool of choice. However, there are some design flaws with CMAB's XmlSerializableHashtable.

This class does a nice job of serializing native types. However, if you attempt to serialize your own classes, you will get a couple of nifty exceptions and the only way around these exceptions are to either modify the delivered source code...or extend the provided classes yourself. I chose the latter and I will attempt to describe how to do this. Also, (Daniel Cazzulino) has a nice blog about XmlSerializer that is a good read if you are interested in some of the details of XmlSerializer...which CMAB uses.

The first exception you might receive is something along the lines of: use the xmlinclude or soapinclude attribute to specify types that are not known statically. To get around this, you can do one of two things:

  • Modify the call to XmlSerializer by passing in the additional types to consider. Or
  • Extend XmlSerializableHashtable by deriving your own type and adding an [XmlIncludeAttribute()] to its definition.

Either method requires that you modify/create source code. I chose the latter because it was the easiest. For example:

[XmlInclude(typeof(RuleAssembly))] <--- I added my class definition
public class RulesSerializableHashtable:XmlSerializableHashtable <--- I derived from this class
{
....
}

A more elegant solution would be to provide a way to add additional class definitions in the configuration file and if i was going to get into the details of doing this, I would read into Daniels blog entry a bit more. However, creating a new class isn't as straightforward as you would think. By creating my own class, i have pretty much broken the configuration section handler provide with CMAB. I now need to create my own because the section handler will cast the values from my xml configuration data to XmlSerializableHashtable...any my derived type, RulesSerializableHashtable, will just get ignored....Oh well. So, to create the section handler, I took what CMAB provided and modified it to cast the hashtable to my derived type....again, i would rather rewrite the code to be more dynamic and i will when i have the time....but the code to do this is pretty straightforward.

So now, I have my own derived serializable hashtable and I rewrote the sectionhandler. Am I done? No, not quite yet...there is still a little work left to do. Some of you may have come across the infamous “Invalid cast exception” (e.g. of type InvalidOperationException).... Now I read a bunch of blogs and comments out there that attribute this to the XmlSerializer. The reality is, the XmlSerializer works just fine.

The problem is located in the indexer of XmlSerializableHashtable. Specifically:

set
{
     lock
( _ht.SyncRoot )
     {
          _ht.Clear();
          foreach( Entry item in value )
          {
               _ht.Add ( 
                    GetValueFromXml(item.EntryKey),
                    GetValueFromXml(item.EntryValue); <---- Not right. If it's an object type, this will go Kaboom
          }
     }
}
The reason is will break is that GetValueFromXml(object val) will attempt to cast this value to an object array because if it is a native type, then its serialized state it is an object array (of xml attributes). However, if it is an object, it is already serialized into the correct object, you merely need to perform a cast. This means, I had to create my own method called GetValFromXml(object val) that checked the type first and if it could not be cast to an object array, it merely returned the passed in object. You could also modify the indexer to do the type checking. either way will work. For example:

protected object GetValFromXml(object value)
{
     if((value as object[])==null)return value;
     ...............
}

Next, I overrode the indexer with the new keyword and changed my set code to call my method. I tried to override the GetValueFromXml but the base class made it a private method so it was hidden from me so i had to override the indexer. Or create my own class altogether and ignore what is provided (not a bad idea really).

To Summarize:

  1. Create your own Serializable Hashtable class. If you derive from XmlSerializableHashtable, make sure you override the indexer.
  2. Make sure your hashtable class either passes in the type to serialize in the constructor of XmlSerializer or use the XmlIncludeAttribute() decoration.
  3. Create your own section handler.

-Mathew Nolton

No Comments