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 library
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:
[XmlRoot("EmailServerSettings")]
public class EmailServerSettings
{
[XmlElement("IP")]
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.
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.
public static class ConfigurationHelper
{
public static T GetSection<T>(string sectionName)
{
return (T)ConfigurationManager.GetSection(sectionName);
}
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:
="1.0" ="utf-8"
<configuration>
<configSections>
-->
<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>
-->
<EmailServerSettings configSource="EmailServerSettings.config" />
</configuration>
The external EmailServerSettings.config file is essentially the XML serialized version of the class.
<!---->
<EmailServerSettings>
<Name>Coldmail</Name>
<IP>127.0.0.1</IP>
</EmailServerSettings>
Now we are ready to actually use all this in code:
EmailServerSettings emailServerSettings1 =
ConfigurationHelper.GetSection<emailserversettings>("EmailServerSettings");
EmailServerSettings emailServerSettings2 =
ConfigurationHelper.GetSection<emailserversettings>();
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:
EmailServerSettings anEmailServer;
anEmailServer = XmlSection<EmailServerSettings>.GetSection("EmailSettings");
Console.WriteLine(anEmailServer);
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);
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, 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.
I work as a Technology Lead for an IT services company based in India.
Passions include programming methodologies, compiler theory, cartooning and calligraphy.