How to: Add CData or Text in a ConfigurationElement

I was working on a MsScriptControl based ETL project. One of its requirements is to be able to use vbscript functions in the transformations it performs. It is fairly easy to hard-code functions then register them during start-up all at the expense of maintainability. I guess the better side of me prevailed and I opted for a more configurable solution.I looked around and found there was no available solution on how to do this; hence this post.

The Target Configuration

For the purpose of illustration, let’s pretend I need to create a system which required me to send emails and store its template and mail server details in the configuration section. The xml below shows the target configuration.

<mailConfiguration server="localhost" port="25">

  <mailTemplates>

    <mailTemplate

        name="template1" 

        sender="user@localhost">

      <receivers>

        <contactInfo

          name="Jake Sta. Teresa"

          email="jake@jakelite.com" />

      </receivers>

      <body>

        <![CDATA[

        Dear Sir,

 

        This is a system generated message.

 

        Thanks,

        OMGWTFBBQ!!!

        ]]>

      </body>

    </mailTemplate>

  </mailTemplates>

</mailConfiguration>

And reading this configuration section will result in something like this.

Using the CDataConfigurationElement and CDataConfigurationPropertyAttribute Classes

I wouldn’t bore you with the details of working with .Net 2.0 configuration classes.  I have another post dedicated to that.

The meat of this post is in the <body> element of the configuration. Its code representation is written below. The highlighted code emphasizes on the classes that are put into play in order to read a cdata or text node from a configuration element.

public class MessageBodyElement

    : cdataElement

{

    [ConfigurationProperty("content", IsRequired = true, IsKey=true)]

    [CDataConfigurationProperty]

    public string Content

    {

        get { return (string)(base["content"]); }

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

    }       

}

The CDataConfigurationPropertyAttribute Class

The CDataConfigurationProperty attribute is only allowed to decorate properties. The code for it can be seen below.

[AttributeUsage(AttributeTargets.Property)]

public sealed class CDataConfigurationPropertyAttribute

    : Attribute

{

}

The CDataConfigurationElement Class

The class CDataConfigurationElement is a subclass of ConfigurationElement. In its constructor, it ensures the following:
•    A property decorated with a CDataConfigurationProperty attribute must be of type string. This will spare us of the trouble of conversion.
•    A property decorated with a CDataConfigurationProperty attribute must be decorated by a ConfigurationProperty attribute. This is only to play nice with the .Net 2.0 configuration classes. Subclassing ConfigurationProperty would have been a better approach but unfortunately its sealed.
•    A class must have only one property decorated with a CDataConfigurationProperty. This is fairly self explanatory. I mean, how do you know which CData element maps to which property?
•    A class which already has a property decorated with a CDataConfigurationProperty can not contain other properties that are decorated by ConfigurationProperty and whose type is subclass of ConfigurationElement. It looks like an orphaned CData element. Just look at this.

<mailConfiguration server="localhost" port="25">

  <mailTemplates>

    <mailTemplate

        name="template1" 

        sender="user@localhost">

      <receivers>

        <contactInfo

          name="Jake Sta. Teresa"

          email="jake@jakelite.com" />

      </receivers>

      <![CDATA[

        Dear Sir,

 

        This is a system generated message.

 

        Thanks,

        OMGWTFBBQ!!!

      ]]>     

    </mailTemplate>

  </mailTemplates>

</mailConfiguration>

After all these validations are performed, the name defined in the corresponding ConfigurationPropertyAttribute of the CDataConfigurationPropertyAttribute is stored in the variable _cDataConfigurationPropertyName.

Overriding SerializeElement and DeserializeElement Methods

In order to read, write to the CData element, we need to override the SerializeElement and DeserializeElement methods. If no property of the CDataConfigurationElement subclass is decorated with a CDataConfigurationProperty attribute, the default serialization/deserialization logic is used. Below are the overrides used in the CDataConfigurationElement:

protected override bool SerializeElement(

    System.Xml.XmlWriter writer,

    bool serializeCollectionKey)

{

    bool returnValue;

    if (string.IsNullOrEmpty(

        _cDataConfigurationPropertyName))

    {

        returnValue = base.SerializeElement(

            writer, serializeCollectionKey);

    }

    else

    {              

        foreach (ConfigurationProperty configurationProperty in

            Properties)

        {

            string name = configurationProperty.Name;

            TypeConverter converter = configurationProperty.Converter;

            string propertyValue = converter.ConvertToString(

                    base[name]);

 

            if (name == _cDataConfigurationPropertyName)

            {

                writer.WriteCData(propertyValue);

            }

            else

            {

                writer.WriteAttributeString("name", propertyValue);

            }

        }                              

        returnValue = true;

    }

    return returnValue;

}

 

protected override void DeserializeElement(

    System.Xml.XmlReader reader,

    bool serializeCollectionKey)

{

    if (string.IsNullOrEmpty(

        _cDataConfigurationPropertyName))

    {

        base.DeserializeElement(

            reader, serializeCollectionKey);

    }

    else

    {

        foreach (ConfigurationProperty configurationProperty in

            Properties)

        {

            string name = configurationProperty.Name;

            if (name == _cDataConfigurationPropertyName)

            {

                string contentString = reader.ReadString();

                base[name] = contentString.Trim();

            }

            else

            {

                string attributeValue = reader.GetAttribute(name);

                base[name] = attributeValue;

            }

        }

        reader.ReadEndElement();

    }          

}

Conclusion

Well there you have it, A working implementation of how to include the text or cdata elements in a ConfigurationElement class. Sources are available here.

Filed under: , ,

Comments

# Erryl said:

"In layman's term please!" =P Hahaha, joke! Nice one boss =D

Monday, March 23, 2009 11:28 PM