Introduction
This article describes how to implement custom objects in the App.Config file, enabling data to be stored in XML format, and to be easily deserialized into a custom object.
Background
There is an assumption that the reader is reasonably familiar with XML Serialization from the System.Xml.Serialization
namespace. For an object to be usable in the App.Config file, it needs to be Serializable
. Refer to Introducing XML Serialization in the MS Help Files.
Using the code
For this example, I have created two classes that will be configured in the App.Config file. The first is a simple class designed to illustrate how to configure a simple class for deserialization, the second is more complex, and involves a collection class as well. I've included the second class and associated classes in the example code, but only describes the first class. The extra complexity is not related to custom configuration handling, but more to XML Serialization, so I leave it up to the reader to read the code in conjunction with the XML Serialization documentation for a better understanding.
For the first example, I have created a class called ProgramInfo
which contains three public properties:
The first two are implemented as public strings, the third uses property assessors. This is a better way to go, I included the first two to illustrate how Serialization works. As long as the element has the same name, it will be found, and it doesn't matter how you implement the property.
The first piece of code to note in the App.Config file is the element telling the CLR that we have a custom configuration section. It is in an element called section
, child of configSections
, direct child of the configuration
document element. It should look like this:
<configuration>
<configSections>
<section name="ProgramInfo"
type="ConfigSectionHandler.ConfigSectionHandler, ConfigSectionHandler" />
</configSections>
</configuration>
The main element of interest is the section
element. This states that there will be an element with the same name as contained in the name
attribute, in this case, ProgramInfo
. The type
attribute states that the CLR should instantiate an instance of the ConfigSectionHandler.ConfigSectionHandler
class, located in the ConfigSectionHandler
assembly. The syntax is the same as that used by the Type
class.
Before we look at the ConfigSectionHandler
class, let's take a look at the ProgramInfo
section further down in the configuration file.
<ProgramInfo type="ConfigSectionObjects.ProgramInfo, ConfigSectionObjects">
<Name>ConfigSectionDemo</Name>
<Language>C#</Language>
<Level>Intermediate</Level>
</ProgramInfo>
This is straightforward XML, but note the type
attribute. This once again uses the same syntax to describe what object should be instantiated to contain the information contained in this XmlNode
. This information is not part of the Configuration system. This complete node, all information contained in it is for use by the ConfigSectionHandler
that we will be using.
Let's look at an abbreviated version of the ProjectInfo
class. The code looks as follows:
namespace ConfigSectionObjects
{
public class ProgramInfo
public ProgramInfo() {
}
public string Name;
public string Language;
private string _Level;
public string Level
{
get {return _Level;}
set {_Level=value;}
}
}
A fairly simple class. I implemented the three properties in two different ways, the first two use public variables, which is not really a good way to handle property access. The last property uses accessor methods.
Note: the combination of the Namespace and the Class name gives us ConfigSectionObjects.ProgramInfo
which is the value we specified in the type
attribute of the ProgramInfo
element in our custom configuration section.
The class name is the same as the element name in the custom section.
The two public variables and the one public property map directly to the children of the ProgramInfo
element. These values all map directly. This does not have to be the case, there are methods to map an element name to a different property, or to have attributes map to properties. These will be discussed briefly, later.
At this point, we have specified in our App.Config file all the information we need, and we have also defined the class that will be deserialized from the App.Config file. Now, we need to use some code to handle the deserialization. To do this, we need to use the GetConfig
static method of the ConfigurationSettings
class, and tell it which section
we want. This method's return type is Object
, so we need to cast it back to the type we require. The code is as follows:
ConfigSectionObjects.ProgramInfo pi=
(ConfigSectionObjects.ProgramInfo)ConfigurationSettings.GetConfig("ProgramInfo");
We declare a variable of the type
that we will be deserializing, and cast the returned object from the method. Now, the variable pi
is a valid instance of ConfigSectionObjects.ProgramInfo
, so we can access its properties like so:
Console.WriteLine ("Program: {0}", pi.Name);
Console.WriteLine ("Language: {0}", pi.Language);
Console.WriteLine ("Level: {0}", pi.Level);
Now, it is worth looking at the actual ConfigSectionHandler
code. It is remarkably simple. It is one method called Create
which is in a class that implements the IConfigurationSectionHandler
interface. This method is called when the line of code we specified above with the GetConfig
call is encountered. The objects passed in are passed in by the CLR. The MSDN documentation states the following:
parent
: the configuration settings in a corresponding parent configuration section.
configContext
: an HttpConfigurationContext
when Create
is called from the ASP.NET configuration system. Otherwise, this parameter is reserved and is a null reference (Nothing
in Visual Basic).
section
: The XmlNode
that contains the configuration information from the configuration file. Provides direct access to the XML contents of the configuration section.
It is only section
that we are interested in. This is the XmlNode
that we defined in the App.Config file. So we need to do a few steps:
- Create an
XPathNavigator
object.
- Use the
XPathNavigator
to get the type
attribute that we have defined in the section
.
- Create a
Type
object based on the attribute value.
- Create an
XmlSerializer
based on the type
.
- Create an
XmlNodeReader
using our custom section
.
- Call
Deserialize
and return the deserialized object.
The code follows:
XPathNavigator xNav=section.CreateNavigator();
string typeOfObject=(string) xNav.Evaluate("string(@type)");
Type t=Type.GetType(typeOfObject);
XmlSerializer ser=new XmlSerializer(t);
XmlNodeReader xNodeReader=new XmlNodeReader(section);
return ser.Deserialize(xNodeReader);
Complex Serialization
It is possible to do more complex serialization. For example, in the ProgramInfo
example, if we wanted to rename the name
element in the ProgramInfo
class to Description
, but leave the App.Config file alone, it is as simple as adding an attribute above the variable declaration. Like this:
[XmlElement("Name")]
public string Description;
This is telling the deserializer, that when the Name
element is found in the configuration section
, then its value should be placed in the Description
property of the ProgramInfo
class.
Alternatively, if rather than having Level
as an element
, you wanted to have it as an attribute
of ProgramInfo
, then, the App.Config file could be changed to remove the Level
element, and add it as an attribute
, then change the ProgramInfo
class to read:
[XmlAttribute("Level2")]
public string Level2;
Once again, refer to the example code, the ServerConfig
section for a more complex example.
Points of Interest
I found I made a lot of errors when defining the type
attributes, I would get the assembly name correct, but forget to fully qualify the class name with the namespace. When this happens, the code will throw a System.Configuration.ConfigurationException
.
I also found the documentation very vague when it came to configuration files.
History
- 20-Apr-2004 - version 1.0.1 - corrected typo about changing level element to an attribute.
- 13-Apr-2004 - version 1.0.0.