Among the myriad of very cool features that .NET 1.0 introduced when it was first launched was the support for XML configuration files. It was really nice to be able to configure the application “on-the-fly” by using these config files and having the application catching up the new settings as soon as the XML configuration was saved to the hard drive. Other than the Framework’s settings, you could add your own settings, easily extending the config file to support the needs of your applications.
Sometimes this power is a bit overlooked, but if we consider the options we had before .NET, which were the Windows Registry, with all its messy API, INI files with all its dumbness and custom made config files with all its complexity, the .NET solution was remarkably clean and elegant. However, there was one thing still missing in the configuration stuff: It wasn’t easy to change these config files at runtime.
Now Microsoft has evolved the configuration model to a state that not only allows you to better define your configuration settings in the config file with much more complex constructs, but also allows you to read and write these settings using a very powerful object model.
Today what I’m going to show you is how to write an application that generates its own configuration settings, save it to the config file and subsequently loads these settings from the config file for application consumption.
The code I will show you below is actually production code on my newest pet project, named NGroups, which is an open source networking toolkit. I will talk more about that in the near future.
The configuration I need for such a project is something like the piece of XML below:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<ngroups enabled="true">
<channel defaultProvider="NChannelProvider">
<providers>
<provider name="NChannelProvider" type="NGroups.NChannelProvider, NGroups.Core" />
</providers>
<protocols>
<protocol ref="UDP" properties="(mcast_addr=228.1.2.3;mcast_port=45566;ip_ttl=32)" />
<protocol ref="PING" properties="(timeout=3000;num_initial_members=6)" />
<protocol ref="FD" properties="(timeout=3000)" />
<protocol ref="VERIFY_SUSPECT" properties="(timeout=1500)" />
</protocols>
</channel>
<protocols>
<protocol name="UDP" type="NGroups.Protocols.UdpProtocol, NGroups.Core" />
<protocol name="PING" type="NGroups.Protocols.PingProtocol, NGroups.Core" />
<protocol name="FD" type="NGroups.Protocols.FdProtocol, NGroups.Core" />
<protocol name="VERIFY_SUSPECT" type="NGroups.Protocols.VerifySuspectProtocol, NGroups.Core" />
<protocol name="UNICAST" type="NGroups.Protocols.UnicastProtocol, NGroups.Core" />
</protocols>
</ngroups>
</configuration>
My steps to create the configuration code were the following: In the first place, I had to know which settings I needed for my application. Once defined the code I needed, I devised an XML format that better represented such settings. I have then written my sample XML file containing the sample configuration I wanted to achieve from the application and then started designing a configuration model that resembled that XML document in code.
The .NET Framework 2.0 implements three classes and a number of attributes that are relevant to the configuration work. The first class is the System.Configuration.ConfigurationSection. This class represents a major section within the configuration file and is most of all a container for all the configuration settings. The next class and probably the most important is the System.Configuration.ConfigurationElement, which represents a configuration element or in other words, the stuff contained within a configuration section. The third class here is the System.Configuration.ConfigurationElementCollection, which represents a collection of similar configuration elements.

On the picture above you can see the inheritance chain for the classes defined in the framework.
On the picture below, you can see how these classes map to the configuration I want to achieve on my application, which makes very easy to understand the code as we write it:

I have then derived the following object model for the configuration settings:

The model may look complex at first glance, but actually it is very simple. Below is an explanation of the actual code.
All the classes are in the namespace NGroups.Configuration. The first one I want to take a look at is the NGroupsSection class, as it is the top level on our config code. The class is implemented as follows:
public sealed class NGroupsSection : ConfigurationSection
{
#region Private Static Variables
private static readonly ConfigurationProperty _propEnabled;
private static readonly ConfigurationProperty _propChannel;
private static readonly ConfigurationProperty _propProtocols;
private static ConfigurationPropertyCollection _properties;
#endregion
#region Constructors
static NGroupsSection()
{
// Define the properties
NGroupsSection._propEnabled = new ConfigurationProperty("enabled", typeof(bool), true, ConfigurationPropertyOptions.None);
NGroupsSection._propChannel = new ConfigurationProperty("channel", typeof(ChannelSettings), null, ConfigurationPropertyOptions.Required);
NGroupsSection._propProtocols = new ConfigurationProperty("protocols", typeof(ProtocolSettingsCollection), null, ConfigurationPropertyOptions.Required);
// Add the properties to the properties collection
NGroupsSection._properties = new ConfigurationPropertyCollection();
NGroupsSection._properties.Add(NGroupsSection._propEnabled);
NGroupsSection._properties.Add(NGroupsSection._propChannel);
NGroupsSection._properties.Add(NGroupsSection._propProtocols);
}
#endregion
#region Public Properties
/// <summary>
/// This property represents the <code>enabled</code> attribute
/// in the configuration file.
/// </summary>
[ConfigurationProperty("enabled", DefaultValue = true)]
public bool Enabled
{
get { return (bool)base[NGroupsSection._propEnabled]; }
set { base[NGroupsSection._propEnabled] = value; }
}
/// <summary>
/// This property represents the <code>application</code> subsection
/// in the configuration file.
/// </summary>
[ConfigurationProperty("channel")]
public ChannelSettings Channel
{
get { return (ChannelSettings)base[NGroupsSection._propChannel]; }
set { base[NGroupsSection._propChannel] = value; }
}
/// <summary>
/// This property represents the <code>protocols</code> subsection
/// in the configuration file.
/// </summary>
[ConfigurationProperty("protocols")]
public ProtocolSettingsCollection Protocols
{
get { return (ProtocolSettingsCollection)base[NGroupsSection._propProtocols]; }
set { base[NGroupsSection._propProtocols] = value; }
}
#endregion
}
The most important pieces of code in this method are first the constructor, where I create instances of System.Configuration.ConfigurationProperty classes. Instances of that class represents configuration properties in the configuration file. These properties are added to a System.Configuration.ConfigurationPropertyCollection object. These properties are also added to the collection on the base class, which stores a key/value pair with key being the instance of ConfigurationProperty and value being the value of that attribute.
I then add three properties to map to the properties I want in the configuration property. They look like ordinary properties, except that they return values from the collection in the base class and they are decorated with the ConfigurationProperty attribute, which is the piece that does the trick. This attribute is used by the System.Configuration.ConfigurationManager to serialize the class into the XML configuration property that we want. The cool thing about that attribute is that if the property it is decorating contains a single value, then it will map such a property as an attribute in the XML element. If the value of the property is of type System.Configuration.ConfigurationElementCollection, then it will map the collection as a sub element in the config file. If you go back to the XML we want to achieve, you will see that Protocols is a sub element with a list of configured protocols.
Then we move on to implement the configuration settings. As they are very similar in their code (actually I have written the first one and then Cut & Paste to the second one), I will show only the more "complex" one, or in other words, the one with more properties. That is as follows:
public sealed class ProtocolSettings : ConfigurationElement
{
#region Private Member Variables
private readonly ConfigurationProperty _propName;
private readonly ConfigurationProperty _propType;
private readonly ConfigurationProperty _propRef;
private readonly ConfigurationProperty _propSettings;
private readonly ConfigurationPropertyCollection _properties;
private NameValueCollection _propertyNames;
#endregion
#region Constructors
public ProtocolSettings()
{
this._propName = new ConfigurationProperty("name", typeof(string), null, ConfigurationPropertyOptions.IsKey);
this._propType = new ConfigurationProperty("type", typeof(string), null, ConfigurationPropertyOptions.None);
this._propRef = new ConfigurationProperty("ref", typeof(string), null, ConfigurationPropertyOptions.None);
this._propSettings = new ConfigurationProperty("settings", typeof(string), null, ConfigurationPropertyOptions.None);
this._properties = new ConfigurationPropertyCollection();
this._properties.Add(this._propName);
this._properties.Add(this._propType);
this._properties.Add(this._propRef);
this._properties.Add(this._propSettings);
}
public ProtocolSettings(string name, string type) : this()
{
this.Name = name;
this.Type = type;
}
#endregion
#region Properties
[ConfigurationProperty("name")]
public string Name
{
get { return (string)base[this._propName]; }
set { base[this._propName] = value; }
}
[ConfigurationProperty("type")]
public string Type
{
get { return (string)base[this._propType]; }
set { base[this._propType] = value; }
}
[ConfigurationProperty("ref")]
public string Ref
{
get { return (string)base[this._propRef]; }
set { base[this._propRef] = value; }
}
[ConfigurationProperty("settings")]
public string Settings
{
get { return (string)base[this._propSettings]; }
set { base[this._propSettings] = value; }
}
public NameValueCollection Parameters
{
get
{
if (this._propertyNames == null)
{
lock (this)
{
if (this._propertyNames == null)
{
this._propertyNames = new NameValueCollection(StringComparer.InvariantCulture);
foreach (ConfigurationProperty property in this._properties)
{
if ((property.Name != "name") && (property.Name != "type")
&& (property.Name != "ref") && (property.Name != "settings"))
{
this._propertyNames.Add(property.Name, (string)base[property]);
}
}
}
}
}
return this._propertyNames;
}
}
protected override ConfigurationPropertyCollection Properties
{
get
{
this.UpdatePropertyCollection();
return this._properties;
}
}
#endregion
#region Private Methods
private string GetProperty(string name)
{
if (this._properties.Contains(name))
{
ConfigurationProperty property = this._properties[name];
if (property != null)
{
return (string)base[property];
}
}
return null;
}
private bool SetProperty(string name, string value)
{
ConfigurationProperty property = null;
if (this._properties.Contains(name))
{
property = this._properties[name];
}
else
{
property = new ConfigurationProperty(name, typeof(string), null);
this._properties.Add(property);
}
if (property != null)
{
base[property] = value;
return true;
}
return false;
}
protected override bool IsModified()
{
if (!this.UpdatePropertyCollection())
{
return base.IsModified();
}
return true;
}
protected override bool OnDeserializeUnrecognizedAttribute(string name, string value)
{
ConfigurationProperty property = new ConfigurationProperty(name, typeof(string), value);
this._properties.Add(property);
base[property] = value;
this.Parameters[name] = value;
return true;
}
internal bool UpdatePropertyCollection()
{
bool isModified = false;
ArrayList newPropertyNames = null;
if (this._propertyNames != null)
{
foreach (ConfigurationProperty property in this._properties)
{
if (((property.Name == "name") || (property.Name == "type")
|| (property.Name == "ref") || (property.Name == "settings"))
|| (this._propertyNames.Get(property.Name) != null))
{
continue;
}
if (newPropertyNames == null)
{
newPropertyNames = new ArrayList();
}
newPropertyNames.Add(property.Name);
isModified = true;
}
if (newPropertyNames != null)
{
foreach (string name in newPropertyNames)
{
this._properties.Remove(name);
}
}
foreach (string name in this._propertyNames)
{
string oldVal = this._propertyNames[name];
string newVal = this.GetProperty(name);
if ((newVal == null) || (oldVal != newVal))
{
this.SetProperty(name, oldVal);
isModified = true;
}
}
}
return isModified;
}
#endregion
}
The class above has some important pieces of code to note. The first one is the constructor: It is not actually different from the one written to the NGroupsSection class, except that it sets the ConfigurationPropertyOptions.IsKey when instantiating ConfigurationProperty for the "name" property. It sets that property as the key in a collection of similar configuration elements. That enumeration has other values such as IsRequired, for example.
We have the same set of properties mapping to the settings we want on the config file, but here we have a bigger set of properties.
The Parameters property is used to return a NameValueCollection containing all the properties specified in the configuration file that don't map to properties in the class. In that way, other implementations of the class using this configuration element can expect additional settings without you having to specify them on the configuration class. They are not strongly typed into properties, but can be accessed from the Parameters collection.
There is then the Properties property. This property is intended to support the configuration infrastructure in the framework and is called to get the list of properties available on that implementation of a configuration element.
I have also a set of methods there. These methods are detailed below:
- GetProperty and SetProperty: These methods are used to get and set respectively values for all the properties in the configuration section. They prove handy when accessing the additional settings not specified in the strongly typed properties.
- IsModified: This method is used by the Framework to determine whether something has changed in this configuration element before saving them on the config file.
- OnDeserializeUnrecognizedAttribute: This method gets called when an attribute in the config file doesn't match a property in the class. Then you have the opportunity to add it to the collection of additional properties.
- UpdatePropertyCollection: This method, when called, loads all the configuration settings in the base class's collection of properties and match then to the collection of names already stored in the instance. If there are additional properties then it will add these properties to the collection of names and return true.
That's pretty much all that is needed to create a configuration element. The last class I'm showing the implementation here is the ProtocolSettingsCollection. That is the collection of ProtocolSettings used in the configuration file. The class is implemented as follows:
[ConfigurationCollection(typeof(ProviderSettings))]
public sealed class ProtocolSettingsCollection : ConfigurationElementCollection
{
#region Private Static Variables
private static readonly ConfigurationProperty _propProtocols;
private static ConfigurationPropertyCollection _properties;
#endregion
#region Constructors
static ProtocolSettingsCollection()
{
ProtocolSettingsCollection._propProtocols = new ConfigurationProperty(null, typeof(ProtocolSettingsCollection), null, ConfigurationPropertyOptions.DefaultCollection);
ProtocolSettingsCollection._properties = new ConfigurationPropertyCollection();
ProtocolSettingsCollection._properties.Add(ProtocolSettingsCollection._propProtocols);
}
#endregion
#region Public Properties
public ProtocolSettings this[int index]
{
get
{
return (ProtocolSettings)base.BaseGet(index);
}
set
{
if (base.BaseGet(index) != null)
{
base.BaseRemoveAt(index);
}
this.BaseAdd(index, value);
}
}
#endregion
#region Methdods
public void Add(ProtocolSettings protocol)
{
if (protocol != null)
{
protocol.UpdatePropertyCollection();
this.BaseAdd(protocol);
}
}
public void Clear()
{
base.BaseClear();
}
public void Remove(string name)
{
base.BaseRemove(name);
}
protected override ConfigurationElement CreateNewElement()
{
return new ProtocolSettings();
}
protected override object GetElementKey(ConfigurationElement element)
{
string key = ((ProtocolSettings)element).Name;
if (string.IsNullOrEmpty(key))
{
key = ((ProtocolSettings)element).Ref;
}
return key;
}
#endregion
The class above is not really different than the other ones on its constructor. I have then added a this[] property to the class so that we can access configuration settings by index and Add, Clear and Remove methods that guess what? They add an item to the collection, clear all the items from the collection and remove a specified item from the collection. You wouldn't guess, would you? :-P
The important methods in this class are CreateNewElement and GetElementKey, which are used by the runtime to correctly instantiate the configuration element needed for this collection.
After having written all this code, the next step is easy: We just create a new Windows or Console application and stuff in some code like this:
NGC::NGroupsSection ngroupsSection = new NGC::NGroupsSection();
ngroupsSection.Enabled = true;
NGC::ChannelSettings channelSection = new NGC::ChannelSettings();
ngroupsSection.Channel = channelSection;
NGC::ProviderSettingsCollection providers = new NGC::ProviderSettingsCollection();
NGC::ProviderSettings provider = new NGC::ProviderSettings();
provider.Name = "NChannel";
provider.Type = "NGroups.NChannel, NGroups.Core";
providers.Add(provider);
channelSection.Providers = providers;
NGC::ProtocolSettingsCollection protocolDefinitions = new NGC::ProtocolSettingsCollection();
for (int i = 0; i < 10; i++)
{
NGC::ProtocolSettings protocol = new NGC::ProtocolSettings();
protocol.Name = "PROT" + i.ToString();
protocol.Type = "NGroups.Protocols.Protocol, NGroups.Core";
protocolDefinitions.Add(protocol);
}
ngroupsSection.Protocols = protocolDefinitions;
NGC::ProtocolSettingsCollection protocolReferences = new NGC::ProtocolSettingsCollection();
for (int i = 0; i < 10; i++)
{
NGC::ProtocolSettings protocol = new NGC::ProtocolSettings();
protocol.Ref = "PROT" + i.ToString();
protocol.Settings = "(setting=1,setting=2,setting=3)";
protocolReferences.Add(protocol);
}
channelSection.Protocols = protocolReferences;
Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
config.Sections.Add("ngroups", ngroupsSection);
config.Save();
Well, the code above just create a number of instances of our classes, set some sample values and use the ConfigurationManager to open the application configuration file, getting a reference to the System.Configuration.Configuration object pointing to the app's config file.
To read from the config file, you can use a code like the one bellow:
Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
NGC::NGroupsSection section = (NGC::NGroupsSection)config.GetSection("ngroups");
Console.WriteLine("NGroups is {0}.", (section.Enabled) ? "enabled" : "not enabled");
Console.WriteLine("\nProviders in the channel:\n");
foreach (NGC::ProviderSettings provider in section.Channel.Providers)
{
Console.WriteLine(provider.Name);
}
Console.WriteLine("\nProtocols in the channel\n");
foreach (NGC::ProtocolSettings protocol in section.Channel.Protocols)
{
Console.WriteLine(protocol.Ref);
}
After running the above code, you should get an output like:
NGroups is enabled.
Providers in the channel:
NChannel
Protocols in the channel
PROT0
PROT1
PROT2
PROT3
PROT4
PROT5
PROT6
PROT7
PROT8
PROT9
Well, that's all folks. I hope you have enjoyed it, if you have any questions just drop a comment here and I will get back to you. I have stripped out the source code of NGroups and posted a zip file that you can get here. It contains only the configuration stuff and the test console application, so you can run it to see the results.