Introduction
XML-based configuration files in .NET are useful placeholders for setting constants acquired by the application at run-time. These include, for example, settings for security, database connection strings or other application specific data. One of the main advantages of using these files is that child web.config files override their parents, the topmost parent file being machine.config residing in the c:/WINNT/Microsoft.NET/Framework/v1.1.4322/CONFIG directory.
While web.config or app.config (its equivalent for a Windows application) are useful in providing standard system-related values at run-time (e.g., information related to assemblies, handlers, tracing, etc.), they are not ideal for holding application data, specifically when you want to share constants across applications. For example, a database connection string is used by several applications of different types (e.g., ASP.NET, Windows Forms, Console applications, etc.). You would ideally like to update each value only in one place as opposed to modifying several files which is a maintenance nightmare.
There are two ways I can think of for solving this problem in .NET without additional code:
- To add
appSettings
keys to the machine.config file which is automatically ensured to be accessible by all the applications on the machine by virtue of machine.config being the topmost configuration file: <appSettings>
<add key="myKey" value="myValue" />
</appSettings>
- By specifying a file to hold the application settings. This can be done by specifying a relative path to an external file containing application configuration settings. The following snippet shows the relevant section in web.config:
<configuration>
<appSettings file="mySettings.config">
</appSettings>
</configuration>
where "mySettings.config" is a custom config file which looks something like this:
<appSettings>
<add key="key1" value="value1" />
<add key="key2" value="value2" />
.....
</appSettings>
Problem with the first approach is that machine.config is a bit dangerous to play with for things which are really of application nature. If you make an inadvertent mistake, you can jeopardize the server and all the sites residing on it. Furthermore, future releases of .NET may overwrite this file and change all of your settings and result in applications stop running.
The second approach is a bit better in that you can point all of your apps or those sharing configuration data to the same file. This does the job since you can access any value like:
String key1 = System.Configuration.ConfigurationSettings.AppSettings["key1"];
The downside with this is that looking at the settings file and without a proper naming scheme, you won't know which value is associated with which project. Another word, the file gives a flat (one-dimensional) representation of key/value pairs which at best can look like this:
<appSettings>
<add key="p1key1" value="value1" />
<add key="p1key2" value="value2" />
<add key="p2key1" value="value1" />
<add key="p2key2" value="value2" />
<add key="p2key3" value="value1" />
....
</appSettings>
where p1 is project 1, p2 is project 2, etc.
This is better than playing with machine.config but hard to maintain specially when application constants proliferate. It is also a convention which has to be enforced on the development team.
There are various solutions for this problem. One that I know of is called "configuration application block" which can be found here. This is a bit of an overkill for this problem, in my honest opinion. In the following section, I present a simple way of resolving this issue.
Using the code
Basically, I'd like to have a two-dimensional web.config file which looks like this:
<configuration xmlns="http://tempuri.org/projects.xsd">
<project>
<projectName>Project1</projectName>
<projectSettings>
<add key="key1" value="cat" />
<add key="key2" value="dog" />
</projectSettings>
</project>
<project>
<projectName>Project2</projectName>
<projectSettings>
<add key="key1" value="horse" />
<add key="key2" value="donkey" />
<add key="key3" value="elephant" />
</projectSettings>
</project>
</configuration>
By two-dimensional, I mean that the first dimension is the project name and the second is the key. So to get the value "dog", you'll access (project1, key2), and to access the value "elephant", you'll access (project2, key3). The C# code to process this is:
using System;
using System.IO;
using System.Xml;
using System.Configuration;
using System.Collections.Specialized;
namespace configUtils
{
public sealed class configReader
{
const string projectsConfigPath = "c:/projects.config";
public static NameValueCollection ProjectSettings
{
get
{
NameValueCollection a = new NameValueCollection();
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(projectsConfigPath);
XmlNode xmlRootNode =
(xmlDoc.GetElementsByTagName("configuration"))[0];
XmlNodeList xmlProjectNodes = xmlRootNode.ChildNodes;
foreach (XmlNode xmlProjectNode in xmlProjectNodes)
{
string projectName = xmlProjectNode.ChildNodes[0].InnerText;
XmlNodeList projectSettingNodes =
xmlProjectNode.ChildNodes[1].ChildNodes;
foreach (XmlNode projectSettingNode in projectSettingNodes)
{
XmlAttributeCollection attributes =
projectSettingNode.Attributes;
XmlAttribute key = (XmlAttribute)
attributes.GetNamedItem("key");
XmlAttribute value = (XmlAttribute)
attributes.GetNamedItem("value");
string k = projectName + "," + key.Value;
string v = value.Value;
a.Add(k, v);
}
}
return a;
}
}
}
}
where your project data is in c:/project.config.
To access the value "elephant", your C# code does:
string animalName = configReader.ProjectSettings["Project2,key3"];
This is a one-liner code for which you don't have to instantiate an object and is pretty simple to implement. Of course you also need a using
statement in your file for this to work:
using configUtils;
This is just a bare-bone version to illustrate the concept, without exception handling and other helping routines.
As is evident in the code above, I am utilizing the NameValueCollection
class to "split" the single argument to a project and a key value. Also, notice that nothing is hard-coded to any specific XML tag (for example, the "project
" tag) and the generic construction of the collection builds the return value. Further additions of projects and key/value pairs to the project.config file doesn't require any code changes or recompilation.
I can think of a couple of enhancements to this code and welcome any suggestions:
- To make the return value look like:
string animalName =
configReader.ProjectSettings["Project2"]["key3"];
in a truly two-dimensional fashion.
- Some caching mechanism so that the collection doesn't get built every time a value is acquired. I am pretty certain that the compiler is wise enough to cache it but think explicit code ensures proper caching.
History