Introduction
This article will provide a little trick for you in dealing with custom configuration sections. Think of it this way - since you're working on an object-oriented fashion with languages within the .NET Framework, and since most of the time you'll be returning XML data your apps will use to create objects your application can use, it stands to reason that using the XmlSerializer
object to read the XML data directly from your config files into objects within your application is a logical "next step". This article will give a quick introduction to the idea behind XML serialization, and provide you with a starting point to use in your own applications.
Getting Started with an Example Object
To get started in this explanation, let's take a look at an example object we'll use. To make everything as simple as possible with a logical-enough example object, we'll create a new Person
object, and give it some pretty minimal properties. The code below outlines this object. Note especially the attributes placed over the class and its properties.
[XmlRoot("Person")]
public class Person
{
string __firstName = String.Empty;
string __lastName = String.Empty;
public Person()
{
}
[XmlAttribute(DataType = "string", AttributeName = "Firstname")]
public string Firstname
{
get { return __firstName; }
set { __firstName = value; }
}
[XmlAttribute(DataType = "string", AttributeName = "Lastname")]
public string Lastname
{
get { return __lastName; }
set { __lastName = value; }
}
}
Pretty simple really. This class' structure is relatively basic. We've used the XmlRoot
attribute to specify that the root node of a Person
object, when serialized, will be "Person". Likewise, we want to represent the two properties as attributes of that root node. The resulting XML code would look something like that below.
<Person Firstname="Brady" Lastname="Gaster"/>
The Configuration Class
Since we're talking about configuration in this article, it stands to reason that we'll need to create a custom configuration reader class, which we'll need to inherit from the interface IConfigurationSectionHandler
. This class is relatively simple; implementation will need to be added to the Create()
method. In this implementation, the code will iterate over a particular node within the application (or web) configuration file and inspect each of the child nodes within it.
For each node located in this XmlNodeList
collection, the node is read into an XmlNodeReader
object, which is then passed into an instance of an XmlSerializer
class. This instance then deserializes the XmlNode
into an instance of a Person
object. Finally, the new object is added into a local variable - an array of Person
objects - via the internal Add
method. Once all of the nodes have been deserialized back into their object state, the array is returned to the calling code. The code sample below demonstrates this implementation.
public class BasicConfigurator : IConfigurationSectionHandler
{
Person[] __people = new Person[0];
public object Create(object parent,
object configContext,
System.Xml.XmlNode section)
{
XmlNodeList nodes = section.SelectNodes("//Person");
for(int i=0; i<nodes.Count; i++)
{
XmlNodeReader rdr = new XmlNodeReader(nodes[i]);
XmlSerializer ser = new XmlSerializer(typeof(Person));
Person p = (Person)ser.Deserialize(rdr);
Add(p);
}
return __people;
}
void Add(Person p)
{
Person[] tmp = new Person[__people.Length+1];
Array.Copy(__people,0,tmp,0,__people.Length);
tmp[__people.Length] = p;
__people = tmp;
}
}
The PersonFactory Helper Class
As pointed out in the MSDN documentation, it's not a great idea to return objects directly from the Create
method. For this reason, a helper class has been added to this code to simplify the retrieval of the collection of Person
objects. This class, the PersonFactory
, does the simple job of using the custom configuration reader implementation and then returning Person
array retrieved from the configuration file to the caller.
public class PersonFactory
{
public static Person[] GetProple()
{
return (Person[])ConfigurationSettings.GetConfig("People");
}
}
Finally, the Config File Structure.
At this point, the code is relatively complete; the last thing needed is for the application to be configured properly so that the PersonFactory
can be used within the code. Without properly adding the configSections
element, the custom tags are pretty much useless. The XML code from the client application contained with the source download is shown below:
="1.0" ="utf-8"
<configuration>
<configSections>
<section name="People"
type="XmlSerializationConfig.BasicConfigurator,
XmlSerializationConfig" />
</configSections>
<People>
<Person Firstname="Brady" Lastname="Gaster"/>
<Person Firstname="Christian" Lastname="Conrad"/>
<Person Firstname="Gina" Lastname="Ostrea"/>
</People>
</configuration>
The first section of this config file - the configSections
area - basically informs the application "for the 'People
' section of this configuration file, use the 'XmlSerializationConfig.BasicConfigurator
' class. It knows what to do and will help you out!" The configurator class then iterates over each of the Person
nodes contained within the People
node, deserializes each node into a real-life Person
object, and adds it to the collection.
The final piece of code we'll take a look at is the client code itself. This code uses the PersonFactory
class to get the collection of People
that have been defined in the configuration.
class Client
{
[STAThread]
static void Main(string[] args)
{
ReadPeopleFromConfiguration();
Console.ReadLine();
}
static void ReadPeopleFromConfiguration()
{
Person[] personArr = PersonFactory.GetProple();
foreach(Person p in personArr)
{
Console.WriteLine("{0} {1}",p.Firstname,p.Lastname);
}
}
}
When this simple console application executes, you'll see that it works perfectly; for each Person
you have identified within the configuration section, a Person
's first and last names will be written to the screen! Not too difficult an example, but one that clearly demonstrates the point! The screenshot below demonstrates the expected results of the client's execution.
Now, you can use more object-oriented techniques in your configuration methodologies!
Happy coding!