Strongly Typed Custom Configuration Sections using XML Serialization






4.33/5 (5 votes)
This article demonstrates a simple approach to get strongly typed configuration objects to use in your code using XML serialization
- Download source - 27.47 KB (using
IConfigurationSectionHandler
) - Download demo - 33.01 KB (using
ConfigurationSection
) - Download demo v1.1 - 24.5 KB (7-Jan-2007, includes use of
SerializerCache
)
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):
- Generic classes
- XML serialization
- 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:
Citrus.Configuration
- Class libraryCitrus.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:
/// <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.
/// <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.
/// <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 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.
<!-- 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:
// 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:
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:
// 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:
<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, XmlSerializer
s 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.