Wednesday, January 25, 2012

Multiple and Multilevel configuration section(The Stupid one)

This is one of my silliest and stupidest code work. Hopefully somebody might benefit from this.

The purpose of this project is to explain a bit of CustomSection Handler in .net and expanding it a bit more to support multilevel section.

Let’s say we have a requirement to create a FileMerger application that is responsible to merge a list of files into a single file. The application should be able to handle any number of group of files and all of this should be configurable in a setting file (This is where the concept was actually born from). I know it sounds a bit silly, but that’s what I said at the first line :).

Let’s say our configuration xml would look something like this, supporting custom sections and multilevel.




    
      
      
      
      
    
    
    
      
      
      
        
        
      

    
    



If you see, the File section can have multiple and multilevel child File sections. So the user can group the files in any way they want.

Let’s see the core of our functionality.

    
public class BaseSectionHandler<t, u> : IConfigurationSectionHandler where T : IBaseSection
    {
        #region IConfigurationSectionHandler Members

        public virtual object Create(object parent, object configContext, System.Xml.XmlNode section)
        {
            var lst = new BaseSectionCollection<t, u>();
            foreach (XmlNode node in section.ChildNodes)
            {
                T item = ReflectionHelper.CreateNewInstance<t>();
                item.InitializeObject(node);
                lst.Add(item);

            }
            return lst;
        }
        #endregion
    }

The BaseSectionHandler implements IConfigurationSectionHandler which is required for your custom configuration handler class to be able to be called back when your custom section in your config file is to be instantiated by ConfigurationManager. IConfigurationSectionHandler provides a Create method which supplies the parent object, if this section is under another section object and instantiated when that section was first instantiated by the ConfigurationManager. The second parameter is Configuration context object and the third one is the root xml node of this configuration section, which is <filestomerge> in our case. The function should return an object and in our case we will be returning a collection of type BaseSectionCollection(our custom List class which we will discuss a bit later.) which will consist of two section objects-BlogAFiles and BlogBFiles.

As you can see that our SectionHandler is generic in type which allows you to extend this for any class type. I’ll explain this again when creating a derived BaseSectionHandler class. Our BaseSectionHandler is for BaseSection section type which encapsulates type U. It’s a bit harder to explain it now. Just remember that our each Xml Node
 
is of type which has FileName and FullPath as properties. The type T should be of IBaseSection. For each xml node we create an instance of type T and call the InitializeObject method of IBaseSection by supplying the xmlnode as it’s parameter.

Now let’s look at the IBaseSection section type.
public interface IBaseSection<T>
     {
        #region properties
        
        [ConfigurationProperty("Key")]
        string KeyField { get; }

        T SectionObject { get; set; }

        BaseSectionCollection<BaseSection<T>, T> ChildSections { get; set; }

        BaseSection<T> this[string key] { get; }

        #endregion properties

        #region virtual methods
        
        string GetPropertyValue(string propertyName);

        void InitializeObject(XmlNode node);

        BaseSection<T> CreateChildObject();

        void DeserializeObject(XmlNode node);

        #endregion
    }

The IBaseSection interface is implemented by BaseSection. Let’s explain the function in BaseSection bit by bit.

public virtual string KeyField
        {
            get
            {
                return "Key";
            }
        }

The KeyField has no significance for our example but it can be used to identify the key field in our custom section node.
public T SectionObject { get; set; }
The SectionObject will hold the instance of type T deserialized from our xml node.
BaseSectionCollection<BaseSection<T>, T> ChildSections { get; set; }
This will hold the collection of child sections if it has any. BaseSectionCollection is nothing but a custom generic List class.
public virtual string GetPropertyValue(string propertyName)
        {
            var ret = ReflectionHelper.GetPropertyValue<BaseSection<T>, string>(propertyName, this);
            if (ret == null)
            {
                if (SectionObject != null)
                {
                    ret = ReflectionHelper.GetPropertyValue<T, string>(propertyName, SectionObject);
                }
            }
            return ret;
        }
This function get’s the property value for type T and set it to the SectionObject, because as I said earlier the SectionObject is of type T.
public virtual BaseSection<T> CreateChildObject()
        {
            return ReflectionHelper.CreateNewInstance<BaseSection<T>>();
            
        }

This function actually instantiates the configuration Section for it’s child nodes. Derived classes can override this to supply an instance of it’s own type.
public virtual void DeserializeObject(XmlNode node)
        {
            ReflectionHelper.DeserializeTypeFromXml<ConfigurationPropertyAttribute>(this, "Name", node);

        }
DeserializeObject function uses reflection helper to deserialize an xml node to it’s type.
public virtual void InitializeObject(XmlNode node)
        {
            DeserializeObject(node);
            SectionObject = ReflectionHelper.CreateNewInstance<T>();
            ReflectionHelper.DeserializeTypeFromXml<ConfigurationPropertyAttribute>(SectionObject, "Name", node);

            foreach (XmlNode xmlNode in node.ChildNodes)
            {
                Type t;

                BaseSection<T> fl = CreateChildObject();

                fl.InitializeObject(xmlNode);

                ChildSections.Add(fl);
            }

        }
InitializeObject function is the heart of our core functionality which Deserialize an xml node into it’s SectionObject. It also tries to deserialize any child nodes and adds them to it’s ChildSections collection. This is how multilevel support has been implemented here. I don’t want to go into details how ReflectionHelper actually deserializes the xmlnode. Just note that the type parameter indicates which type of attribute should be looked up in each property of SectionObject type. The parameters are the object into which the xml node should be deserialized into. The second parameter is the property name of the attribute which should be looked up to determine the xml attribute from which the property it decorates should be initialized with the value.

Now let’s see how these base classes can be leveraged to provide a custom section handler and custom section to handle our requirement. If you look at our xml configuration(<File FileName="BlogAFile1" FullPath="{AppPath}\BlogFiles\BlogAFile1.txt" />) you can see that each node should consist of FileName and FullPath attribute.

Let’s create a class which directly maps this xml node.
public class MergeFile
    {
        [ConfigurationProperty("FileName")]
        public string FileName { get; set; }

        [ConfigurationProperty("FullPath")]
        public string FullPath { get; set; }
    }

If you recall this statement:
ReflectionHelper.DeserializeTypeFromXml<ConfigurationPropertyAttribute>(SectionObject, "Name", node);
We have used the ConfigurationProperty attribute to decorate our object’s property. The name parameter supplied in configurationProperty attribute is used to map this property with the xml node’s attribute.

Now we need to create our custom BaseSection type which encapsulates MergeFile.
public class MergeFileSection : BaseSection<MergeFile>
    {

        public override string KeyField
        {
            get
            {
                return "FileName";
            }
        }

        
        public override BaseSection<MergeFile> CreateChildObject()
        {
            var section = new MergeFileSection();
            return section;
        }
    }

We have overridden the CreateChildObject function in BaseSection to instantiate it’s own type. And changed the KeyField to “FileName”.

Now we need our custom configuration handler derived from BaseSectionHandler to encapsulate our MergeSection.
public class MergeFilesSectionHandler : BaseSectionHandler<MergeFileSection, MergeFile>
    {

    }
That’s it.

Now let’s create our custom config section in app.config. The final app.config looks something like this.


  
    
The configSections under consists of all the custom config sections with it’s xml node name and configuration handler type which will be called to deserialize the xml when this section is accessed using ConfigurationManager.GetSection() function. Remember the handler should implement IConfigurationHandler and supply the Create() method, which is what out BaseSectionHandler does.

You can easily get the mapped list from your custom configuration section as.
static void Main(string[] args)
        {
            var ret = (BaseSectionCollection<MergeFileSection,MergeFile>)System.Configuration.ConfigurationManager.GetSection("FilesToMerge");

            foreach (var section in ret)
            {
                MergeFiles(section);
            }
            
        }
 
The source code is attached herewith. Please look into the code for implementation details.