Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Extend ConfigurationElement to Deserialize Text Elements

5.00/5 (2 votes)
3 Jan 2010CPOL3 min read 49.3K   398  
This article describes how to extend the ConfigurationElement class to deserialize text elements.

ConfigurationTextElement

Introduction

This article describes how to extend the ConfigurationElement class to deserialize text elements. By default, this class and its handful of derivatives only handle elements with attribute data. Brief instructions are given for deserializing custom configuration sections with attribute data. Once that framework is in place, a complete solution with text element deserialization is provided.

Background

Before .NET 2.0, IConfigurationSectionHandler could be used to deserialize all types of configuration XML. Since 2.0 was released, that interface has been deprecated and a new set of classes and attributes have been provided in its place. The primary replacement class is ConfigurationElement, and examples of its use are readily available. Unfortunately, all of those examples only deserialize XML with attribute data. While most XML could be rewritten to move text content into attributes, some of the expressiveness of the XML itself is lost.

Using the Code

The following namespaces will need to be referenced and included:

C#
using System.Collections.Generic;
using System.Configuration;
using System.Xml;

All classes in this example have been written within the following namespace definition:

C#
namespace ConfigurationTextElement
{
}

Part 1 - Common Example

Add a custom section to your project's main configuration file. In this example, the namespace and assembly for the project is called ConfigurationTextElement. The custom section is called root, and will be stored in a file named root.config.

XML
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="root" 
      type="ConfigurationTextElement.Root,ConfigurationTextElement"/>
  </configSections>
  <root configSource="root.config"/>
</configuration>

If you do choose to store the root XML in a separate file, be sure to change the Copy to Output Directory property of the file to Copy Always. This will ensure that the file will be present in the binary folder that is used during debugging.

XML
<?xml version="1.0" encoding="utf-8" ?>
<root>
  <element1 attrib1="x" attrib2="y"/>
  <!--
  <element2>x</element2>
  <element3>
    <child1 key="x" value="a"/>
    <child1 key="y" value="b"/>
  </element3>
  <element4>
    <child2>e</child2>
    <child2>f</child2>
  </element4>
  -->
</root>

When debugging classes for custom configuration sections, elements that do not yet have the corresponding code will cause exceptions to be thrown. For this reason, it's easier to design the XML structure and populate it in its entirety, but comment out all XML which is not yet ready to be tested.

Create the classes which will handle the deserialization of the XML and store its data. The first class will correspond to the root element, which also represents the configuration section.

C#
using System.Configuration;

public class Root : ConfigurationSection
{
}

Create a lazy singleton class which exposes an instance of the root class in a static property. In this example, our configuration classes will be consumed directly by our application.

C#
using System.Configuration;

namespace ConfigurationTextElement
{
    public class Settings
    {
        private static Root _root = null;
        
        private Settings() { }

        public static Root Root
        {
            get
            {
                if(_root==null)
                {
                    _root = (Root)ConfigurationManager.GetSection("root");
                }
                return _root;
            }
        }
    }
}

Create the class that will store element1, adding the appropriate base class and property attributes.

C#
public class Element1 : ConfigurationElement
{
    [ConfigurationProperty("attrib1")]
    public String Attrib1 { get { return this["attrib1"].ToString(); } }
    [ConfigurationProperty("attrib2")]
    public String Attrib2 { get { return this["attrib2"].ToString(); } }
}

Update the root class to include a property for element1.

C#
public class Root : ConfigurationSection
{
    [ConfigurationProperty("element1")]
    public Element1 Element1 { get { return (Element1)this["element1"]; } }
}

Add code to the OnLoad method of either a page or form to test the values of attrib1 and attrib2.

C#
protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);

    string e1a1;
    e1a1 = Settings.Root.Element1.Attrib1;
}

The previous element is pretty easy to implement, and is very similar to many other examples that are available. The next element, element2, is not quite so straightforward. In fact, trying to deserialize it with a ConfigurationElement will yield the following error:

The configuration section cannot contain a CDATA or text element.

Part 2 - Extending ConfigurationElement

Create a new class called ConfigurationTextElement which is generic, inherits from ConfigurationElement, and overrides the DeserializeElement method of its base class. This class is also a generics class which casts and returns the data according to the type specified at instantiation.

C#
using System.Collections.Generic;
using System.Configuration;
using System.Xml;

public class ConfigurationTextElement<t> : ConfigurationElement
{
    private T _value;
    protected override void DeserializeElement(XmlReader reader, 
                            bool serializeCollectionKey)
    {
        _value = (T)reader.ReadElementContentAs(typeof(T), null);
    }
    public T Value
    {
        get { return _value; }
    }
}

The remaining classes for this example are defined as follows:

C#
public class Root : ConfigurationSection
{
    [ConfigurationProperty("element1")]
    public Element1 Element1 { get { return (Element1)this["element1"]; } }

    [ConfigurationProperty("element2")]
    public ConfigurationTextElement<string> Element2 { 
      get { return (ConfigurationTextElement<string>)this["element2"]; } }

    [ConfigurationProperty("element3")]
    [ConfigurationCollection(typeof(Child1),AddItemName="child1")]
    public Element3 Element3 { get { return (Element3)this["element3"]; } }

    [ConfigurationProperty("element4")]
    [ConfigurationCollection(typeof(ConfigurationTextElement<string>), 
     AddItemName = "child2")]
    public Element4 Element4 { get { return (Element4)this["element4"]; } }
}

public class Element1 : ConfigurationElement
{
    [ConfigurationProperty("attrib1")]
    public String Attrib1 { get { return this["attrib1"].ToString(); } }
    [ConfigurationProperty("attrib2")]
    public String Attrib2 { get { return this["attrib2"].ToString(); } }
}

public class Element3 : ConfigurationElementCollection
{
    protected override ConfigurationElement CreateNewElement()
    {
        return new Child1();
    }
    protected override object GetElementKey(ConfigurationElement element)
    {
        return ((Child1)element).Key;
    }

    public Child1 this[int index]
    {
        get { return (Child1)BaseGet(index); }
    }
    new public Child1 this[string key]
    {
        get { return (Child1)BaseGet(key); }
    }
    public bool ContainsKey(string key)
    {
        List<object> keys = new List<object>(BaseGetAllKeys());
        return keys.Contains(key);
    }
}

public class Element4 : ConfigurationElementCollection
{
    protected override ConfigurationElement CreateNewElement()
    {
        return new ConfigurationTextElement<string>();
    }
    protected override object GetElementKey(ConfigurationElement element)
    {
        return ((ConfigurationTextElement<string>)element).Value;
    }
    public ConfigurationTextElement<string> this[int index]
    {
        get { return (ConfigurationTextElement<string>)BaseGet(index); }
    }
}

public class Child1 : ConfigurationElement
{
    [ConfigurationProperty("key")]
    public String Key { get { return this["key"].ToString(); } }
    [ConfigurationProperty("value")]
    public String Value { get { return this["value"].ToString(); } }
}

Uncomment all of the XML within the custom configuration section. The following code should now give you your configuration settings:

C#
protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);

    string e1a1;
    string e2;
    string e3c1_item1;
    string e3c1_item2;
    string e4c2_item1;
    string e4c2_item2;

    e1a1 = Settings.Root.Element1.Attrib1;
    e2 = Settings.Root.Element2.ToString();
    e3c1_item1 = Settings.Root.Element3[0].Value;
    e3c1_item2 = Settings.Root.Element3["y"].Value;
    e4c2_item1 = Settings.Root.Element4[0].Value;
    e4c2_item2 = Settings.Root.Element4[1].Value;
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)