Configuration settings make it possible for users and administrators to configure an application before it is run for the first time and while it runs.
.NET provides a good rudimentary mechanism for storing and retrieving configuration settings in the application's .config file with the ConfigurationSettings class, but this mechanism is missing a number of essential features. This article describes how to improve on the base functionality using a class that provides strong typing, allows for writing of keys, and provides optional encryption of keys.
I consider configuration information a vital component of any application and use it extensively for allowing customization of the application both at runtime and through external configuration settings. I try to make as many options user-configurable as possible and configure everything from user interface elements, to top level business logic options, all the way to developer options that allow me to do things like switch in and out of detailed debug modes, turn on logging or tracing, and so on. A Configuration class is not exactly a sexy feature, but it's quite vital to the usability and configurability of an application.
The .NET ConfigurationSettings class, although easy to use, still requires a fair amount of code to access settings reliably.
It should be easy to create and maintain configuration information. If you have to write a lot of code or remember every setting, you'll end up not using configuration settings all that much, resulting in an application that isn't as configurable as it could be.
What .NET Provides
Natively, .NET provides a rudimentary mechanism through the ConfigurationSettings.AppSettings class. This class reads key values from a special section in the application's .config file. Here's a section in Web.config after some keys were added.
<configuration>
<appSettings>
<add key="ConnectionString"
value="server=(local);database=WebStore;
trusted_connection=true;
enlist=false;" />
<add key="ConnectType" value="SqlServer" />
<add key="TaxRate" value="0.0400" />
<add key="TaxState" value="HI" />
<add key="ItemFormListPageCount" value="10" />
<add key="SendAdminEmail" value="False" />
... more settings
</appSettings>
</configuration>
All the information in the AppSettings section is stored in string format. You can retrieve this information easily by using the ConfigurationSettgins.AppSettings object:
string ConnectionString =
ConfigurationSettings.AppSettings["ConnectionString"];
It seems easy enough. And it is easy if:
- The value you're retrieving is a string, and
- The value exists in the configuration file.
If the key doesn't exist, the value is returned as null. So if you plan on using the value above, you need to make sure that you add a null check. If you look at the configuration example above, you'll also notice that there are several values that are non-string types: TaxRate is a decimal, SendAdminEmail is a Boolean, and ConnectType is actually an Enum value. The AppSettings object only returns strings and so you have to convert these strings back into numeric, Boolean, or enum type values. The code to do this safely within your code gets long quickly. Here's an example of picking up a decimal value and converting it.
decimal TaxRate = 0.00;
string cTaxRate = Configuration.AppSettings["TaxRate"];
if (cTaxRate != null)
{
try
{
TaxRate = decimal.Parse(cTaxRate);
}
catch {;}
}
If you have to write this much code each time you want to use a non-string value, it becomes unwieldy quickly. In my experience, if configuration settings aren't easy to access, you won't use them and you'll end up hard-coding stuff that should be configurable.
Improving Settings with Class
The base functionality in .NET is a big improvement over no configuration features at all, but it leaves a lot to be desired in usability terms. Luckily, you can very easily extend the functionality and provide a better interface, building on top of the existing functionality. The wwAppConfiguration class works in conjunction with the .NET ConfigurationSettings class and provides the following improvements:
- A typed interface to configuration settings
- Default values returned if a key is not set
- Default value automatically written into the .config file
- Read and Write access to keys
- Error handling for invalid entries
- Encryption for specific keys
- Ability to use non .config files for storage
How It Works
The main goal of the ConfigurationSettings class is to provide a standard, strongly typed class interface to configuration settings. The reasoning behind this is clear: strong typing catches any type of naming errors at compile time so there are no runtime surprises. Further, you can use IntelliSense to access the settings, so you don't have to jump to your Config file to remember what the key names are (if they're there).
Static constructors fire exactly once for a given class, when a static field is accessed on the class.
To provide this functionality, you create a new subclass of the wwAppConfiguration class and add the fields or properties that become your configuration settings. The class takes these properties and persists them into the configuration file, reading and writing the data as needed, shuttling data between the configuration file and the class.
The wwAppConfiguration class relies heavily on inheritance. There's an abstract class (wwAppConfiguration) that provides the worker interface. This abstract class provides the logic to read and write configuration settings into the public members of the class.
Your subclass only adds the public properties or fields that make up the configuration settings. Reading of the data happens automatically when you instantiate the class. To avoid having to reload this data each time you need it, it's best to store the object to a globally accessible static property of some sort. There will be more about this later.
To implement a class with the settings in the web.config file shown earlier, use the code shown in Listing 1.
That's all it takes! Now you can simply instantiate your class and start reading properties to get configuration information stored in the .config file:
WebStoreConfig Config = new WebStoreConfig();
string ConnectionString = Config.ConnectionString;
decimal TaxRate = Config.TaxRate;
bool ProcessCards = Config.ccProcessCardsOnline;
ServerTypes ConnectType = Config.ConnectType;
Note that you get fully typed values back from the value stored in the configuration, if any. Property values are converted to the proper type automatically. There is no manual parse code. Even enum values are converted. If a single value doesn't exist in the .config file (because you added a property, perhaps), the default value you assigned to the field in the class definition is returned and the key is written into the .config file. If a value is invalid and the type cannot be converted, the default is returned as well.
If you run this code for the first time and did not create the AppSettings section in the .config file, the section is automatically created and all the entries from this class are added automatically. This is nice, as you know if you've ever debugged a large number of entries and accidentally mistyped one of the keys and were wondering why a value wasn't reading properly. Here, the keys are always automatically created for you so there's no chance of mistyping anything. This makes it much easier to deploy your application for the first time as you don't have to remember to fill the AppSettings section in your installation: the defaults get there automatically the first time the Configuration class is accessed.
Note that if you're using ASP.NET, writing to the web.config file requires that you give write access rights to the ASPNET/NETWORK SERVICE account. If writing is not enabled, the default values are not written and you have to manually enter the keys or use a link that allows impersonation to save and update settings. There's more info on this topic in my WebLog at http://west-wind.com/weblog/posts/295.aspx.
Use a Static Member to Hold the Configuration Object
The example above uses a new class instance to load the WebStoreConfig class. Although this works, it's also very inefficient and takes extra work on your part. You really don't want to have to create an instance of this class each time you need to access one or more values. Nor do you want the class to have to retrieve and convert each of the values each time you instantiate it.
Instead it's best to use a static member to store this Configuration object. By using a static member, you can create the Configuration object once and leave it on the static property, which is always accessible to the application. Using a static constructor (see sidebar) it's possible to have the Configuration object load up automatically the first time that it is needed.
So, let's hook up our Configuration object somewhere as a static member. I like to use a class called App in all of my applications. I use App rather than any of the preexisting Framework classes (such as the Global class in ASP.NET applications) so that I can reuse the object in multiple applications regardless of whether it is an ASP.NET or WinForm application. I usually store this class in my business objects project. A simple example of the App class with the Configuration object is shown in Listing 2. Normally, this class has other "global" static properties that are used in the applications, but for demonstration, I only showed the Configuration member.
Notice the use of a static constructor (static App()). This constructor fires the first time you access the Configuration object or any other static property or field of this class. Static constructors fire exactly once for the lifetime of the application and on the first access to any object of that particular class instance, which makes them very useful as loaders for properties?just like the configuration object?that require only a one time initialization. With this constructor in place, you can use the following syntax from anywhere in your code:
decimal TaxRate = App.Configuration.TaxRate;
There's no object instantiation and no fuss, and you end up with a cached Configuration object. Static properties and static constructors are a very cool and useful feature.
Changing Properties of the Configuration Object
You can also set properties of the Configuration object by simply assigning a value:
App.Configuration.TaxRate = 0.05M;
Keep in mind that when you're setting a value, it is only changed in memory and not automatically persisted back into the configuration file. If you want to write all changes to the configuration to disk, you can call the WriteKeysToConfig() method.
App.Configuration.WriteKeysToConfig()
This method is static so you also don't need an instance to call it. If you are running ASP.NET, calling this method updates Web.config, which has the same effect as manually editing Web.config; ASP.NET shuts down and restarts the currently executing Application (unloads and reloads the AppDomain) to reflect the changes that were made to Web.config.
The wwConfiguration class allows you to access a configuration setting as a property.
This behavior of ASP.NET is very useful. The application shuts down and restarts, and your static constructor gets fired again when you first access the App.configuration object. This means you lose nothing by using this class: you still get notified of changes in web.config even when the file is manually edited or edited by another program. Cool!
Remember that in a multi-threaded environment, a static instance is shared among all threads. This means that all ASP.NET threads see the App.Configuration object. You need to be careful when writing data to properties or fields. Configuration information is mostly read, not updated, so you probably don't have to lose any sleep in this case, but keep this in mind as you're dealing with static properties in general.
Alternate Configuration Storage with Serialization
You can also write the configuration information to an alternate store instead of a config file and its AppSettings section. By calling an alternate constructor, you can prevent the class from reading values on load, and instead use serialization to read and write the contents of your object. I find, for WinForms applications, it's actually easier to use this mechanism because of the way app.config works (or doesn't work <g>) inside Visual Studio. VS .NET overwrites the config file each time the application runs, which makes it impossible to persist settings written at runtime, so using a different file to store settings makes development with these settings easier.
All you need to do to create a separate XML file is to create the class like this:
[Serializable]
public class WebStoreConfig : wwAppConfiguration
{
// *** Override the constructor to do nothing!
public WebStoreConfig(bool NoLoad)
{
}
... property interface here
}
Add the Serializable attribute and add a new constructor that doesn't call back into the base class, to prevent the default from loading from the configuration file. To write out the configuration data, you can use a method in the wwUtils class in your source code that allows XML (or binary) serialization:
wwUtils.SerializeObject(App.WebStoreConfig,FileName,false);
and deserialization, like this:
App.configuration = wwUtils.DeSerializeObject(FileName,typeof(WebStoreConfig),
false);
if (App.configuration == null)
App.configuration = new WebStoreConfig();
If deserialization returns null, the file didn't exist or the XML format was incorrect. In that case, you need to create a new instance and use the default values. This mechanism uses standard .NET serialization to write and read to an XML file. Using Serialization is very easy and one of the very handy features of the .NET Framework. You can review the handful of lines of code in the wwUtils class of the source provided.
The wwAppConfiguration Class
Let's take a look under the hood and see how this class works. The wwAppConfiguration class implements two main worker methods, ReadKeysFromConfig() and WriteKeysToConfig(). Their functions aren't hard to guess. They shuttle data between the class interface and the config file.
The Read version uses Reflection to loop through all the fields and properties of the class and reads the values out of the ConfigurationSettings.AppSettings object. It performs conversions and checks to make sure that all keys exist in the configuration file. If they don't, a flag is set that causes the settings to be written out at the end. Listing 3 shows the code for the ReadPropertiesFromConfiguration.
The default constructor of the class automatically calls the ReadKeysFromConfig() method, which causes the properties of the class to be loaded with the values from the configuration file. The method then proceeds to use the MemberInfo collection and the .NET ConfigurationSettings.AppSettings class to loop through each of the fields of the object and retrieve a matching value from the AppSettings NameValueCollection. Because you need to check both properties and fields, you need to use both FieldInfo and PropertyInfo collections, making the code a little more messy than it should be as there are conditional uses of either of these classes to set values.
I use the wwUtils.SetPropertyEx method to simplify the property or field setting code: this method automatically checks to see whether the item is a property or field and then uses the appropriate FieldInfo or PropertyInfo class with its appropriate Set method. This method also has logic to drill down multiple object levels using "." object syntax. You can check out this useful method and several other Reflection helpers in the supplied code in the wwUtils class.
If a value is not found in the AppSettings (null return), a flag is set notifying the code that keys are missing and need to be written out when you're finished. If a value is found, that value is read and then converted into the proper type for the field. Conversion usually involves using the Convert object except for a few types like string and Boolean, which can be converted more directly. The last type conversion is for enums. The code checks to see if the field or property is an enum and, if it is, uses the extremely handy Enum.Parse() method to turn the string value into an Enum value.
And so you loop through each field and assign its value with the matching entries from the configuration file. If there are extra keys in the configuration file, they are ignored. If there are missing keys in the configuration file, MissingFields is set and you call WriteKeysToConfig() to write the current configuration out to the configuration file.
Writing Keys to the Configuration File
The WriteKeysToConfig() method is used to write values back into the configuration file. It's used internally, but you can also call it externally to cause all of your current configuration settings to be written after you've made changes to the object.
Your web.config changes automatically cause ASP.NET to restart the currently running application. Using this class to write keys doesn't change this behavior.
The logistics of this method are a little different from the read method, primarily because .NET doesn't provide an interface to write values back to the configuration file. ConfigurationSettings.AppSettings is a read-only interface. So in order to write out data, you'll have to do this the hard way by using XML directly. In the WriteKeysToConfig method shown in Listing 4, I use the XmlDocument class to read the entire .config file and update the AppSettings section structure.
As in the read code, the MemberInfo collection is used to loop through each of the fields in the object. But here you check against nodes that exist in the XML document using XPATH expressions. If a node is missing, it's created including (possibly) the top level AppSettings node. When the update is complete, the whole .config file is written back out to disk.
In addition to the internal requirement to write out the keys when they don't exist, it's also useful to be able to do this programmatically or interactively through the UI. Almost every application needs to be able to edit configuration settings, and editing an XML file is not my idea of a user-friendly interface. For example, Figure 1 shows a Web interface for some of the keys you were looking at earlier, and that are, in this case, databound directly against the App.Configuration object.
Any change made in the UI automatically updates the fields in the Configuration object and you can then simply write it out into the configuration. The btnSave code for this form is shown in Listing 5.
Because you have strong types and an object instance, the databinding can occur directly against the properties of your Configuration object and data can be bound directly back. (Note: This example uses databinding classes described in a previous article: "Implementing Two-Way Data Binding in ASP.NET" (CoDe Magazine, Nov/Dec 2003) but it also works with standard databinding mechanisms). All you need to do is write out the data with WriteKeysToConfig() when you're finished. It's that simple: the hardest part is creating the UI.
In Windows Forms, it's even easier if you use Properties (not fields, as I've done in the examples above) for your configuration settings. You can use the PropertyGrid to display your data simply by assigning your Config object to the SelectedObject of the PropertyGrid; you don't even need to write any code. If you add Component attributes, you can even automatically group and attach descriptions to your settings; remember that in order for the property grid to see your properties, you need to use properties:
[Description("The name for the Store displayed
in headers, confirmations etc."),
Category("Company Information")]
public string StoreName
{
get { return this.cStoreName; }
set { this.cStoreName = value; }
}
string _StoreName = "West Wind Web Store";
Using a class has obvious advantages, both from a usability point of view and from a UI point of view, because you have something that you can bind to easily.
Encryption
The final feature of the class that I want to demonstrate is encryption of selected keys. A lot of configuration information is essential, so you might want to protect the .config file by encrypting sensitive keys. The purpose is to prevent people from physically looking into the web.config file and gaining access information for, say, the database connection string. Instead, this information should only be available for editing through the application itself and, most likely, through password-protected access forms that require a valid log on.
One big thing missing from the .NET ConfigurationSettings class is the ability to write values back out to the .config file.
To add encryption, I use a simple support class called wwEncrypt that is provided with the source for this article. This class uses two-way DES encryption to encrypt and also decrypt specific values.
In order to implement this functionality, I added two private properties to the wwAppConfiguration base class:
private string EncryptFieldList = "";
private string EncryptKey = "";
EncryptFieldList is a comma-delimited list of fields that you want to have encrypted. The EncryptKey is the Key used to encrypt and decrypt the fields as they are read from the configuration and out to disk in the ReadKeysFromConfig/WriteKeysToConfig methods.
There's also a special Constructor and a SetEncryption() method used to set these fields. This Constructor looks like this:
public wwAppConfiguration(string EncryptFields,
string EncryptKey)
{
this.SetEncryption(this.EncryptFieldList,
EncryptKey);
this.ReadKeysFromConfig();
}
If you want more control, you can also create the object and call the SetEncryption() method directly
WebStoreConfig Config = new WebStoreConfig(true);
Config.SetEncryption("ConnectionString,MailPassword",
"SuperSecret");
Config.SetConfigurationSection("MyApplication");
this.ReadKeysFromConfig();
Note the constructor call with a Boolean value parameter which stops the default loading of configuration settings until you explicitly call ReadKeysFromConfig().
The ReadKeysFromConfig method and the WriteKeysToConfig method then add a couple of lines of code to handle the encryption and decryption as part of the Member loops that go through each of the properties. Here's the relevant Read code, which fires immediately after you retrieve the value from the AppSettings
if (Value != "" && this.EncryptFieldList.IndexOf("," +
Fieldname + ",") > -1 )
Value = _
wwEncrypt.DecryptString(Value,this.EncryptKey);
Here, you decrypt the string and store the decrypted value in the object. On the Write end, the process is reversed. Immediately after reading the value from the property and converting it to a string, it is encrypted:
if (this.EncryptFieldList.IndexOf("," +
Field.Name.ToLower() + ",") > -1)
Value = wwEncrypt.EncryptString(Value,this.EncryptKey);
With the right routines in place, this process is really easy. You can find the EncryptString and DecryptString methods in the wwEncrypt class in the source.
In order for you to enable this encryption for your own subclass, all you have to do is implement a custom Constructor that calls back to the base class and passes the EncryptFields and the EncryptKey.
public WebStoreConfig() : base(false)
{
this.SetEncryption("
this.ReaadKeysFromConfig();
}
Of course, you can also just call the two-parameter Constructor directly, but generally, I prefer to have this done all in one place and forget about it.
How Secure Is This?
Because the encryption relies on a key phrase to provide encryption, it is possible to hack the code and retrieve the password. It's recommended that you use something a little less obvious than WebStoreAppPassword for your encryption key. Needless to say, the value of this key has to be stored somewhere and it's up to you to decide how to make the key secure.
Encryption occurs when keys are written out to the .config and decryption occurs when keys are written back to the object. This means that the in-memory object has full, unencrypted access to the data in the configuration file. So while your application runs, the values are available. If your UI displays these values, it's highly recommended that you password protect any forms that let you access this data. On a Web Form, use Windows, Basic, or Forms Authentication to require a login before displaying or allowing edits of the data.
This solution is meant to be a prevention mechanism for casual discovery by just browsing the configuration file, but it's obviously not bullet proof. Somebody hacking into the source can figure out how to get around the encryption pretty easily; using the classes in the assembly actually accomplishes this hacking task. Unless the architecture of the application is known though discovering the key or using the class to read, the data is not obvious and so deters casual snooping. The other half of this is your application: if you allow editing of configuration values, you also need to make sure you protect the pages of the application that make these settings available.
Configure It
Maintaining configuration information should be easy; I hope this Configuration class simplifies the process to the point that you use configuration and customization options frequently. Having this information always available, along with the help of IntelliSense, makes this a big improvement over the native functionality of the .NET ConfigurationSettings class. And remember: the more configurable your application, the more flexible it is.
As always, if you have any questions or comments you can contact me at rstrahl@west-wind.com or, even better, on our message board at: http://www.west-wind.com/wwThreads/Default.asp?Forum=Code+Magazine