Friday, February 20, 2009

.NET – Application Configuration using XmlSerializer

The ConfigurationManager class provides access to configuration files for client applications. However creating and using a custom configuration section represented by a specific Xml schema is not so straight forwards.

Here I will present a simple way to persist an object as an entire configuration section using XmlSerializer. This implementation does not interfere with other configuration sections, utilizes the ConfigurationManager class to retrieve the section, etc.

Part 1 – The End Result

What this technique going to achieve is the ability to utilize complex application configuration objects in your application in a type safe manner and without all the overhead of properties classes auto-generated by Visual Studio. I will call this application configuration object "ConfigModel" from here on. In the example provided here the ConfigModel object has a string Output property. The application is a console application and all it does is write the value of Output to the console.




using System;
namespace ConfigTest
{
class Program
{
static void Main(string[] args)
{
CustomConfigModel configModel = ConfigModel.GetConfig();

if (null == configModel)
return;

Console.WriteLine(configModel.Output);
}
}
}


Obviously this is a very simple example, but the point is that the confgModel object has a property named Output and the rest of the application should not need any consideration of how to use the configModel object; just assume that it exists (and not that it came from an app.config file).

Part 2 – How Was ConfigModel Created?

The instance of the ConfigModel object in this example is created by deserializing the Xml representation of the configuration section during an implementation of IConfigurationSectionHandler.Create(). Simply put, here's some code:





using System;
namespace ConfigTest
{
public abstract class ConfigModel : System.Configuration.IConfigurationSectionHandler
{
#region IConfigurationSectionHandler Members

public object Create(object parent, object configContext, System.Xml.XmlNode section)
{
if (null == section)
throw new System.Configuration.ConfigurationErrorsException("Configuration failed to load. The config section in null.");

if (string.IsNullOrEmpty(section.ToString()))
throw new System.Configuration.ConfigurationErrorsException("Configuration failed to load. The config section in TestBaseConfigSectionHandler is empty.");

System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(this.GetType());
System.Xml.XmlReader reader = new System.Xml.XmlNodeReader(section);
ConfigModel configModel = serializer.Deserialize(reader) as ConfigModel;

return configModel;
}

#endregion

public static TCustomConfigModel GetConfig()
where TCustomConfigModel : ConfigModel
{
foreach (System.Xml.Serialization.XmlRootAttribute xmlRootAttribute in typeof(TCustomConfigModel).GetCustomAttributes(typeof(System.Xml.Serialization.XmlRootAttribute), true))
{
return System.Configuration.ConfigurationManager.GetSection(xmlRootAttribute.ElementName) as TCustomConfigModel;
}
return System.Configuration.ConfigurationManager.GetSection(typeof(TCustomConfigModel).Name) as TCustomConfigModel;
}
}

[System.Xml.Serialization.XmlRoot("ConfigModel")]
public class CustomConfigModel : ConfigModel
{
private string output = "Hello World";
public string Output { get { return this.output; } set { this.output = value; } }
}
}




A few notes about the preceding code:

For example, in C / C++ this perfectly valid:

int X = 0;

if (X = 1) printf("hello world");

The result here is that you will ALWAYS evaluate to true. … And X will be assigned the value of "1". Some (including myself) consider assignment during a conditional statement a useful feature. Problem is that most of the time I really want to evaluate the condition as "X is equal to 1". What I meant was:

If (X == 1) printf("hello world");


Consider the alternative:

int X = 0;

if (1 == X) printf("hello world");

This does exactly the same thing to evaluate "X is equal to 1", expressing exactly what I meant. However at 4 am the following typo is very common:

If (1 = X) printf("hello world");

Fortunately "1" is a constant and a compiler error will let me know that I didn't mean what I said.

  • In the constructor for XmlSerializer I use this.GetType() instead of typeof(ConfigModel) because ConfigModel is abstract and I really want to deserialize CustomConfigModel
  • If the name of the configuration section is not the same as the name of the config model class (i.e. CustomConfigModel) then the config model class MUST have an XmlRootAttribute that describes the name of the configuration section.
  • There is no requirement for ConfigModel to be abstract, but in the real world it is useful to make ConfigModel reusable, so I made CustomConfigModel to inherit from ConfigModel and describe specific properties.
  • The ConfigModel class has a static generic GetConfig() method. There's a little bit of logic in there to assist with getting the name of the configuration section from the XmlRootAttribute of the derived class. I did it this way because I would not want the calling application to figure out the name of the config section; this is just as well because the restrictions about the name of this section are imposed by ConfigurationManager, not by this code example's implementation.

Part 3 – The Application Configuration File

Perhaps the most error-prone part of this solution is making sure the app.config is correct in the configSection, however it is something that is not done very often in development, and there are ways to get visual studio to write these sections for your, or programmatically create / modify application configuration and call the Configuration.Save() method (http://msdn.microsoft.com/en-us/library/ms134087.aspx).

Anyway, the application configuration file needs to define a section and its type. For example:



<?xml
version="1.0"
encoding="utf-8" ?>

<configuration>

<configSections>

<section
name="ConfigModel"
type="ConfigTest.CustomConfigModel, ConfigTest"/>

</configSections>


<ConfigModel>

<Output>Hello World</Output>

</ConfigModel>

</configuration>




Above, the type attribute is telling the ConfigurationManager where to find IConfigurationSectionHandler. The interface could have been put on any class but I just put it in ConfigModel for simplicity.

Also, the <ConfigModel> Xml node is the root of the document used for deserialization. Whatever object types can be expressed by XmlSerializer can be used here.

Summary:

There are a few places where this is very useful. First off is that it's never a good idea to hard-code data into an application. Secondly, implementing your own ConfigurationElement derived classes are painful and impose more specific constraints on how to use those objects.

Maybe in future posts I describe

  • more complex ConfigModel derived classes, and how they look in the app config
  • using ConfigModel to describe parameters for unit tests (and other unit test stuff like automated unit testing and tests for stored procedures and other SQL objects)
  • using IConfigurationSource from the Microsoft Enterprise Library, and the above Xml using a SQL Configuration Source and xml data type
  • Using multiple configuration sources and merging config sections (useful when you have very large config files but only need to configure a couple of really small things, or when you have a common configuration for a bunch of applications)
  • Configuration editor GUI / loading and saving sections; extending visual studio

No comments:

Post a Comment