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, 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.