Here's a little technique that I created for reading values out of an application configuration file; be it an app.exe.config file or a web.config file, the strategy works the same. While I am very appreciative of the ease that as .NET developers we have of getting up off the ground and reading values from an application configuration file (along the lines of what we did in unmanaged code with GetPrivateProfileString() for .ini files) I am frequently burdened by having to deal with the standard
<appSettings>
<add key=“blah“ value=“blah, blah, blah“ />
</appSettings>
There's really nothing inherently wrong with this. It's fast. It's easy. It's convenient. It's easily readable. It's easily retrievable:
string val = ConfigurationSettings.AppSettings[”blah”];
For the vast majority of applications this works out just fine. That said, I'll point out that I'm kinda particular when it comes to my software - anyone who works with me would undoubtedly concur. I'm a perfectionist when it comes to writing my code - sometimes to a fault.
The <add key=... /> tag just isn't all that descriptive and it makes your code cluttered with calls to ConfigurationSettings, breaking down the OO model to some extent. If you want the OO feel, then this just doesn't cut it. Sure, you can create a wrapper class that as it's properties are called, delegates the call on to the ConfigurationSettings object, but that, too, is clumsy as well as laborious to create.
This is where configuration sections enter the picture. The .NET framework has carefully (and conveniently) provided a set of objects and interfaces that we can use to control the loading of configuration values. Now, rather than the <add key=”blah” value=”abc” /> tag, we can use proper XML and say <blah>abc</blah>. All we have to do is just a bit of wireup and we'll call it good. In order to properly use custom configuration sections we have to make changes to the .config file as well as our application.
In order to create a custom section within the .config file, we must create what is known as a Configuration Section Handler. A class that implements the IConfigurationSectionHandler interface will suffice. This interface has a single method called Create() which is called by the .NET Framework when it's time to load the custom section. It's up to us to parse the Xml contained within our custom node...but there's a cooler, fancier way than simply parsing the Xml as we'll see here shortly.
First, let's declare our custom configuration section and associate our IConfigurationSectionHandler to it:
<configuration>
<configSections>
<sectionGroup name="devstone">
<section name="user" type="MyApp.MySectionHandler, MyApp" />
</sectionGroup>
</configSections>
</configuration>
The Xml code above declares that within the .config file there will be a group named devstone and within that group will be found a section (XmlNode) called user. When loaded, the .NET Framework will automatically create an instance of the MyApp.MySectionHandler class found in the MyApp assembly. The MySectionHandler class implements the IConfigurationSectionHandler interface.
Ok, this is pretty standard stuff, but let's get down to the code that actually reads the section and show some cool techniques. The section handler code is all found within the IConfigurationSectionHandler.Create() method. Reading the Xml from the file will be easy. So easy, in fact, that we won't do it at all - directly. In fact, we'll rely on the .NET Framework to do it for us. The trick is to create an object that serializes out to the same Xml structure found in our configuration section. But, how will the Framework know which object to create in order to deserialize our Xml section properly? We'll embed the type name in the Xml node.
By doing this we can create a generic section handler that will simply read this type name from the node and deserialize the Xml to the designated object. Here's the code for the new IConfigurationSectionHandler class:
public sealed class MySectionHandler : IConfigurationSectionHandler {
object IConfigurationSectionHandler.Create(object parent, object configContext, XmlNode section) {
// inspired by code by Marc Focas
// http://www.thecodeproject.com/csharp/ConfigSectionHandler.asp
XPathNavigator nav = section.CreateNavigator();
string objType = (string)nav.Evaluate("string(@type)");
XmlSerializer ser = new XmlSerializer(Type.GetType(objType));
return ser.Deserialize(new XmlNodeReader(section));
}
}
That's all there is to it. Now let's check out the custom section and its associated class.
<configuration>
<configSections>...</configSections>
<devstone>
<user type="MyApp.UserSettings, MyApp">
<name>Aaron</name>
<title>Supreme Overlord</title>
<user>
</devstone>
</configuration>
The class that drives this is the Employee class found within the MyApp application:
[XmlRoot("user")]
public sealed class UserSettings {
private string _name;
private string _title;
public Employee() {
/* a public, parameterless constructor is required for serialization to work properly */
}
[XmlElement("name")]
public string Name {
get { return _name; }
set { _name = value; }
}
[XmlElement("title")]
public string Title {
get { return _title; }
set { _title = value; }
}
}
Note the usage of the XmlRootAttribute and the XmlElementAttribute. They are not strictly necessary, but it's important in this instance to use them because of the case. Were I not to put the attributes on, the Xml would need to match case- and spelling-wise the properties and the class name. I prefer camelCased Xml so I explicitly put the attributes on while maintaining the PascalCasing for my code.
Well, this is pretty slick! Now comes the trick of loading the configuration section at runtime. In order to achieve this, we can use the ConfigurationSettings.GetConfig() method. But where do we perform the call? And more importantly, where do we store the deserialized object? Do we make it a global object? Do we recreate it anytime we need access to a property?
I believe this is an excellent example of when a singleton object should be used. At any given time we only have one set of settings for the application. To achieve this, we'll modify the UserSettings class that we just created a bit:
[XmlRoot("user")]
public sealed class UserSettings {
private static UserSettings _user = null;
public static UserSettings Current {
get {
if ( null == _user ) {
lock ( typeof(UserSettings) ) {
if ( null == _user ) _user = ConfigurationSettings.GetConfig("devstone/user") as UserSettings;
}
}
return _user;
}
}
//...the rest of the code still goes here
}
Now, the section will be loaded the first time the UserSettings class is accessed. The call to GetConfig("devstone/user") instructs the .NET Framework to load the devstone section group at the user section. It will, in turn, and by virtue of the <configSections /> Xml block, load our MySectionHandler class which inspects the Xml, thereby deserializing our UserSettings class. Now in order to retrieve the user name in the .config file we make the following call in our code:
string name = UserSettings.Current.Name;
That's a lot more attractive and much more object-oriented than:
string name = ConfigurationSettings.AppSettings["Name"];
A very powerful usage of this model is that of creating components. It's a powerful paradigm to be able to drop your own company-specific sections in a .config file and have the classes necessary to read them. This way, you won't have to go mucking up someone else's .config file and potentially clashing with their already extant names. It's provides a much more professional feel to your application.
Enjoy!