Loading stuff out of a .NET configuration file is so simple, a monkey could do it. Yeah, it’s simple to use, and most of my projects use the application configuration file (App.Config or Web.config) for one thing or another.
Like the last post, I’d like to show a couple of lines that make me cringe:
<configuration>
<appSettings>
<!-- test -->
<add key="url" value="http://dev.cyborg.com/synthesize.asmx"/>
<!-- production
<add key="url" value="http://cyborg.com/synthesize.asmx"/>
-->
</appSettings>
</configuration>
As you can see, there are two copies of the same value, for a url that the application is to connect to. During development and test, the test entry is uncommented, and once the application is deployed, the production entry is supposed to be uncommented. I was burned by this practice recently, when I deployed an application to production and forgot to make sure that the correct entry was uncommented.
Not only is this practice stinky, but it leads to further messiness when you need to marshal entries into native types: int, decimal, anything other than a string. What I wanted was a simple way to accomplish three things:
- configure sets of values for different runtime environments: test, production, etc.
- automatically marshal strings into native types
- switch runtime environments with a single change
I predict that some might feel that what I’m about to present amounts to overkill. Maybe so – a little - but I’ll never be burned again by forgetting to switch out comments to change environments! Apologies aside, let’s start by looking at another configuration file:
<configuration>
<appSettings>
<add key="EnvKey" value="X"/>
<!-- Global keys - loaded for all environments -->
<add key="Year" value="2010"/>
<add key="TempFolder" value="C:\Temp\"/>
<!-- Production environment keys -->
<add key="P.ServiceUrl" value="http://prod.service.com/salestax.asmx" />
<add key="P.ServiceId" value="SuperProdId1" />
<add key="P.Password" value="xyzProd123" />
<add key="P.Rating" value="98.1521" />
<!-- Test environment keys -->
<add key="T.ServiceUrl" value="http://dev.service.com/salestax.asmx" />
<add key="T.ServiceId" value="SuperTestId1" />
<add key="T.Password" value="xyzText123" />
<add key="T.Rating" value="88.8751" />
<!-- Local Test environment keys -->
<add key="X.ServiceUrl" value="http://localhost/salestaxmock.asmx" />
<add key="X.ServiceId" value="SuperLocalId1" />
<add key="X.Password" value="xyzLocal123" />
<add key="X.Rating" value="78.2485" />
</appSettings>
<connectionStrings>
<add name="P.AppConnection" connectionString="...production..."/>
<add name="T.AppConnection" connectionString="...test..."/>
<add name="X.AppConnection" connectionString="...local..."/>
</connectionStrings>
</configuration>

This file defines some entries with a prefix of P, T, or X, and other entries have no prefix at all. In this way, I define sets of entries that are to be used for production or test. The first entry, EnvKey, defines which environment is active.
I would have preferred to write extension methods to the .NET ConfigurationManager class, but it’s not possible to extend static classes; instead I wrote a class called ConfigurationManagerEx, and a set of methods to load individual values or create an object from them.
ConfigurationManagerEx
The first methods in the class load individual target values from the appSettings or connectionStrings sections.
Configuration entries
The first thing you might like to do with this class is to load a target value from the appSettings section, and to convert it to a desired type. To load the value for the Year entry and convert it to an integer, for instance, you would use:
var year1 = ConfigurationManagerEx.AppSettings<int> ("Year");
There is an override to this method, which provides a default value for the case that the key is not present in the file:
var year2 = ConfigurationManagerEx.AppSettings<int> ("Year", DateTime.Now.Year);
This method loads the key if it is present, but if it is not found, it provides the default year as the current year from DateTime.Now.
The second thing you might want to do is to load a value from the active environment. To load the ServiceUrl entry, you would use:
var serviceUrl = ConfigurationManagerEx.EnvAppSettings ("ServiceUrl");
Like the previous method, there is an override to provide a default value.
For connection strings, there is only a single method, which returns entries from the connectionStrings section for the active environment.
var connectionString = ConfigurationManagerEx.EnvConnectionString ("AppConnection");
There is no method to return a connection string without the EnvKey prefix – for that, use the ConfigurationManager class.
Configuration entities
The final bit for this class is the creation of entity objects. In order to prepare for this, I created a simple class with properties that reflect the entries that I’m managing in the appSettings and connectionStrings sections of the configuration file.
public class AppEntity
{
public int Year { get; set; }
public string TempFolder { get; set; }
public string ServiceUrl { get; set; }
public string ServiceId { get; set; }
public string Password { get; set; }
public decimal Rating { get; set; }
public string AppConnection { get; set; }
}
To create the entity, I use the CreateEntity method.
var cfgInfo = ConfigurationManagerEx.CreateEntity<AppEntity>();
After the method call, the AppEntity object is loaded with the two appSettings entries that are not environment-specific, Year and TempFolder, and all of the values that correspond to the active environment.
So there you have it. Hopefully this will help someone avoid environmental heartburn.
Tags: configurationmanager, appsettings, connectionstrings