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

Strongly Typed Custom Configuration Sections using XML Serialization

4.33/5 (5 votes)
7 Jan 2008CPOL3 min read 1   713  
This article demonstrates a simple approach to get strongly typed configuration objects to use in your code using XML serialization

Introduction

This article describes a simple mechanism to use XML serialization within configuration files to support strongly typed configuration items in your projects.

Background

This article uses the following concepts (and readers are expected to have a basic understanding of the same):

  1. Generic classes
  2. XML serialization
  3. Handling configuration files

Using the Code

Note: The code is provided as two separate solution file downloads, one uses the IConfigurationSectionHandler interface (which is deprecated in .NET v2.0 and above), the other uses the ConfigurationSection class to implement the same. Both projects are made up of two class libraries:

  1. Citrus.Configuration - Class library
  2. Citrus.Configuration.Demo - Demo application

Strongly typed configuration entities are very much advantageous in terms of code clarity and readability, ease of programming, etc. Consider a hypothetical situation where we need to save the name and IP of an email server within our configuration file. We need to be able to retrieve these details from the configuration file as strongly typed entities. Meaning, we could do with a EmailServerSettings object that has two properties Name and IP that can be accessed off of it.

Of course this is just an example, and the approach can be easily reused and extended for configurable entities of your choice.

The first thing is to define the EmailServerSettings class that represents our strongly typed entity:

C#
/// <summary>
/// This is a sample class that is used
/// to describe XML serialization based configuration
/// section usage
/// </summary>
[XmlRoot("EmailServerSettings")]
public class EmailServerSettings
{
    [XmlElement("IP")]
    // note the use of implicit property accessors in C# 3.0
    public string IP { get; set; }

    [XmlElement("Name")]
    public string Name { get; set; }

    #region Overrides
    public override string ToString()
    {
        return String.Format("{0} email server ({1})", Name, IP);
    }
    #endregion
}

This is really straightforward. To allow for XML serialization, we mark up the properties and class with the necessary XML attributes.

.NET 1.1 Approach

Next, we define a generic XML serialization based configuration section handler. What this class does is it implements the IConfigurationSectionHandler interface. This interface is deprecated in .NET version 2.0 and above. For implementing the same in .NET 2.0, refer to the section .NET 2.0 Approach.

C#
/// <summary>
/// Handles the access to a generic configuration section using
/// XML serialization
/// </summary>
/// <typeparam name="T">The type of the configuration section</typeparam>
public class XmlSectionHandler<T> : IConfigurationSectionHandler
{
    #region Properties and fields

    private XmlSerializer serializer = new XmlSerializer(typeof(T));
    public XmlSerializer Serializer { get; set; }

    #endregion

    #region IConfigurationSectionHandler Members

    public virtual object Create(object parent, object configContext, XmlNode section)
    {
        return serializer.Deserialize(new StringReader(section.OuterXml));
    }

    #endregion
}

XmlSectionHandler is a generic class; it uses the type associated with it to create a serializer that does the core XML deserialization.

At this stage, we have the fundamental blocks required; a simple helper class that gives us the strongly typed configuration entities is also written.

C#
/// <summary>
/// Configuration helper class that exposes generic
/// section retrieval functionality
/// </summary>
public static class ConfigurationHelper
{
    /// <summary>
    /// Retrieves a typed configuration section for
    /// the current application's default configuration
    /// </summary>
    /// <typeparam name="T">
    /// The type to bind to the configuration section</typeparam>
    /// <param name="sectionName">
    /// The name of the configuration section</param>
    /// <returns></returns>
    public static T GetSection<T>(string sectionName)
    {
        return (T)ConfigurationManager.GetSection(sectionName);
    }

    /// <summary>
    /// Retrieves a typed configuration section for
    /// the current application's default configuration
    /// </summary>
    /// <typeparam name="T">
    /// The type to bind to the configuration section</typeparam>
    /// <returns></returns>
    public static T GetSection<T>()
    {
        return (T)ConfigurationManager.GetSection(typeof(T).Name);
    }
}

All the helper class does is provide some useful routines that cast the configuration objects to the specified type. That's it. These three classes form the core classes in the Citrus.Configuration class library.

To use them, first we need to update our configuration file. The following is a simple configuration file that uses EmailServerSettings and XmlSectionHandler in a custom config section:

XML
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <!-- The following is used to describe our custom section -->
    <section name="EmailServerSettings"
type="Citrus.Configuration.XmlSectionHandler`1
      [[Citrus.Configuration.EmailServerSettings,
      Citrus.Configuration,
      Version=1.0.0.0,
      Culture=neutral,
      PublicKeyToken=null]],
      Citrus.Configuration,
      Version=1.0.0.0, Culture=neutral,
      PublicKeyToken=null" />
  </configSections>
  <!-- Now that our section has been declared, use it -->
  <EmailServerSettings configSource="EmailServerSettings.config" />
</configuration>

The external EmailServerSettings.config file is essentially the XML serialized version of the class.

XML
<!-- A simple XML file containing settings for a email server -->
<EmailServerSettings>
  <Name>Coldmail</Name>
  <IP>127.0.0.1</IP>
</EmailServerSettings>

Now we are ready to actually use all this in code:

C#
// The following will give us the email server settings
EmailServerSettings emailServerSettings1 =
	ConfigurationHelper.GetSection<emailserversettings>("EmailServerSettings");
EmailServerSettings emailServerSettings2 = 
	ConfigurationHelper.GetSection<emailserversettings>();

// Print the information out
Console.WriteLine(emailServerSettings1);
Console.WriteLine(emailServerSettings2);

In fact you can easily use emailServerSettings1.Name and emailServerSettings1.IP, giving us strongly typed configuration entities.

To extend this for your requirements, you would need to define your own configuration specific classes (like the EmailServerSettings class) and add an appropriate configuration section in your config file.

.NET 2.0 Approach

Since the IConfigurationSectionHandler is deprecated in this version, we have to redefine the XmlSectionHandler class as one that derives from ConfigurationSection instead:

C#
public class XmlSection<T> : ConfigurationSection where T: class

(Note the class name has changed in the .NET 2.0 version). Subtle modifications have to be made to allow the class to handle XML serialization in this case. These changes include overriding the Init() and DeserializeSection() methods of ConfigurationSection. Also, XmlSection implements a basic support to save modified configuration details back to the configuration file. To use the XmlSection class, refer to the code snippet below:

C#
// The object that represents our email server's settings
EmailServerSettings anEmailServer;
// Get the section that is specified an external configuration file
anEmailServer = XmlSection<EmailServerSettings>.GetSection("EmailSettings");
Console.WriteLine(anEmailServer);

// Update the section, refresh and load it again
Config.Configuration c = Config.ConfigurationManager.OpenExeConfiguration
			(Config.ConfigurationUserLevel.None);
XmlSection<EmailServerSettings>.GetSection("EmailSettings", c).Name = "Hello";
c.Save();
Config.ConfigurationManager.RefreshSection("EmailSettings");
anEmailServer = XmlSection<EmailServerSettings>.GetSection("EmailSettings");
Console.WriteLine(anEmailServer);

// Get settings that are specified inline
anEmailServer = XmlSection<EmailServerSettings>.GetSection("ProxyEmailServer");
Console.WriteLine(anEmailServer);

The corresponding config file is as follows:

XML
<configuration>
    <configSections>
        <section name="EmailSettings"
type="Citrus.Configuration.XmlSection`1
[[Citrus.Configuration.EmailServerSettings, Citrus.Configuration]],
Citrus.Configuration"/>
        <section name="ProxyEmailServer"
type="Citrus.Configuration.XmlSection`1
[[Citrus.Configuration.EmailServerSettings, Citrus.Configuration]],
Citrus.Configuration"/>
    </configSections>
    <EmailSettings configSource="EmailSettings.config" />
    <ProxyEmailServer>
        <Name>Proxy</Name>
        <IP>127.0.0.1</IP>
    </ProxyEmailServer>
</configuration>

Let me know if all this is helpful in some way to you. Enjoy.

History

[ .] Initial version
[+] New release. .NET 2.0 specific implementation using the ConfigurationSection class
[+] 7-Jan-2008. (v1.1)  
In the previous version, XmlSerializers were created on the fly using the XmlSerializer(Type, XmlRootAttribute) constructor. By nature of design, using this constructor is not very efficient. Therefore, a simple SerializerCache is implemented that caches XmlSerializer objects so that they can be reused, making usage faster and efficient. Another minor change made was to move the EmailServerSettings class outside of the Citrus.Configuration library.

License

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