Stuck? Need help? Ask questions on our forums.
*/

Other Views

corner
*/

Building a better .NET Application Configuration Settings Class

Building a better .NET Application Configuration Settings Class

By Rick Strahl
West Wind Technologies
rstrahl@west-wind.com

Configuration settings are an important part to any non-trivial application. They make it possible for users of the application to configure it both before the application is run for the first time and even while the Application is running. .NET provides a good rudimentary mechanism of 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. In this article I'll describe how we can improve on the base functionality that .NET provides by providing a class that makes it easy to add new keys, provides strong typing, allows for encrypting of keys and the ability to make changes to the settings at runtime and write them back out into the configuration file.

I consider configuration information a vital component of any application and I use it extensively for allowing customization of the application both at runtime and through external configuration settings. I try to make as many options as possible user configurable and configure everything from user interface elements, to some 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.

This is not exactly a sexy feature, but one that is quite vital to the usability and especially the configurability of an application. It should be easy to create and maintain this configuration information. If you have to write a lot of code or have to remember every setting in your head, you end up not using them as much as you should, which often results in an application that isn't as configurable as it could be.

What .NET provides

.Net natively provides us with 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 this section in Web.config for example after some keys were added:

Listing 1 - A typical User Configuration Section in Web.config

<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 easily retrieve this information by using the ConfigurationSettgins.AppSettings object:

string ConnectionString = ConfigurationSettings.AppSettings["ConnectionString"];


Easy enough. That is if:

  • The value you're retrieving is a string
  • 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 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 a Boolean, and ConnectType actually an enum value. The AppSettings object only returns strings and so you have to convert these strings back into a numeric, Boolean or enum typed values. The code to do this safely in your code gets long quickly. Listing 2 shows an example of for picking up a decimal value and converting it.

Listing 2 - Using the AppSettings object to convert a non-string value safely gets lengthy quickly

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 end up hard coding stuff that should be configurable.

Improving Configuration Settings with Class

The base functionality that .NET provides 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 this functionality and provide a better interface building on top of the existing functionality. I'm going to introduce the wwAppConfiguration class which works in conjunction with the .NET ConfigurationSettings class. It provides the following improvements:

  • A typed interface to configuration settings
  • Default values that also get written to 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 this class is to provide a standard, strongly typed class interface to configuration settings. To do this you create a new subclass of the wwAppConfiguration class, and simply add fields or properties, which become your configuration settings. The class then 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. It 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. More about this later.

To implement a class with the settings shown in the web.config file from Listing 1 we'd use the code shown in Listing 3.

Listing 3 - Implementing a wwAppConfiguration subclass with custom config settings

using Westwind.Tools;
public class WebStoreConfig : wwAppConfiguration
{
      public string StoreName = "West Wind Web Store";
      public Westwind.BusinessObjects.ServerTypes ConnectType = 
             Westwind.BusinessObjects.ServerTypes.SqlServer;
      public string ConnectionString = "";
      public decimal TaxRate = 0.04M;
      public string TaxState = "HI";
      public bool ccProcessCardsOnline = false;
}


That's all it takes! Now you can simply instantiate your class and start reading properties to get configuration information stored from 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 we get fully typed values back from the value stored in the configuration if any. Property values are converted to the proper type automatically. 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 maybe - the default value that you assigned the field in the class definition is returned and the key is written into the .config file.

If you run this code for the first time and you 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, because 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 by accident.

ASP.Net Security Note
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 to hold the Configuration object
The example above uses a new class instance to load the WebStore Config class. While 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 we want the class to have to retrieve and convert each of the values each time when we instantiate it.

Instead we can use a static member to store this Configuration object. By using a static we can create the Configuration object once and leave it on the static property which is always accessible to the application. Using a static constructor it's possible to have the Configuration object load up automatically the first time that it is needed.

To do this we need to 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 whether it is a ASP.Net or WinForm application. I usually store this class in my business object project. A simple example of the App class with the Configuration object is shown in Listing 4. Normally this class would have other 'global' static properties that are used in the applications, but for demonstration I only show the Configuration member.

Listing 4 - Hooking up the Configuration object to a static property with a static constructor

public class App 
{
      public static WebStoreConfig Configuration;
      static App() 
      {
            Configuration = new WebStoreConfig();                 
      }
}


Notice the use of a static constructor (static App()). This constructor fires the first time you access the Configuration object. 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 our configuration object that require only a one time initialization. With this constructor in place you can now use the following syntax from anywhere in your code:

decimal TaxRate = App.configuration.TaxRate;


No object instantiation, no fuss and a cached Configuration object.

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 will update 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.

This behavior of ASP.Net is very useful. If you manually edit the web.config file the same thing occurs. The app shuts down and restarts and your static constructor gets fired again when you first access the App.configuration object. This means loose nothing by using this class - we 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 between all threads. This means the App.configuration object is seen by all ASP.Net threads for example. You need to be careful when writing data to properties/fields. Configuration information is not likely to be in contention so you probably don't have to loose 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 out to an alternate store instead of to 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>) in Visual Studio.

All you need to do 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 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.

The wwAppConfiguration Class

Let's take a look behind the covers and see how this class works. The class' functionality is straightforward. It implements two main worker methods: ReadPropertiesFromConfiguration() and WriteKeysToConfig(). Their function isn'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 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 which causes the settings to be written out at the end. Listing 5 shows the code for the ReadPropertiesFromConfiguration.

Listing 5 - Reading properties from the .config file in the wwAppConfiguration class

public abstract class wwAppConfiguration
{
      public wwAppConfiguration() 
      {
            this.ReadKeysFromConfig();
      }
      public void ReadKeysFromConfig() 
      {
            Type typeWebConfig = this.GetType();
            MemberInfo[] Fields = typeWebConfig.GetMembers(
                       BindingFlags.Public | BindingFlags.Instance );
            // *** If we have missing fields write them out at end
            bool MissingFields = false;
            foreach(MemberInfo Member in Fields) 
            {
                  string TypeName = null;
                  FieldInfo Field = null;
                  PropertyInfo Property = null;
                  if (Member.MemberType == MemberTypes.Field)
                        Field = (FieldInfo) Member;
                  else if (Member.MemberType == MemberTypes.Property)
                        Property = (PropertyInfo) Member;
                  else
                        continue; // Skip methods, events etc.
                  if (Field != null)
                        TypeName = Field.FieldType.Name.ToLower();
                  else
                        TypeName = Property.PropertyType.Name.ToLower();
                  string Fieldname = Member.Name.ToLower();
                  string Value = ConfigurationSettings.AppSettings[Fieldname];
                  if (Value == null) 
                  {
                        MissingFields = true;
                        continue;
                  }
                  if (TypeName == "string")
                        wwUtils.SetPropertyEx(this,Fieldname,Value);
                  else if (TypeName.StartsWith("int") )
                        wwUtils.SetPropertyEx(this,Fieldname,Convert.ToInt32(Value));
                  else if (TypeName == "boolean") 
                        if (Value.ToLower() == "true") 
                              wwUtils.SetPropertyEx(this,Fieldname,true);
                        else
                              wwUtils.SetPropertyEx(this,Fieldname,false);
                  else if (TypeName == "datetime")
                        wwUtils.SetPropertyEx(this,Fieldname,Convert.ToDateTime(Value));
                  else if (TypeName == "decimal") 
                        wwUtils.SetPropertyEx(this,Fieldname,Convert.ToDecimal(Value));
                  : More types omitted here
                  else if(Field != null  && Field.FieldType.IsEnum)
                        wwUtils.SetPropertyEx(this,Fieldname,
                                              Enum.Parse(Field.FieldType,Value) );
                  else if (Property != null  && Property.PropertyType.IsEnum)
                        wwUtils.SetPropertyEx(this,Fieldname,
                                              Enum.Parse( Property.PropertyType,Value) );
            }
            // *** We have to write any missing keys
            if (MissingFields)
                  this.WriteKeysToConfig();
      }
}


The constructor of the class automatically calls the ReadKeysFromConfig() method that causes the properties of the class to be loaded with the values from the configuration file. The ReadKeysFromConfig method then proceeds to use Reflection using 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 we need to check both properties and fields we need to use both FieldInfo and PropertyInfo collections which makes 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 of field setting code - this method automatically checks to see whether the property is a property or field and then uses the appropriate FieldInfo or PropertyInfo class with its appropriate Set method. The 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 that notifies our code that keys are missing and need to be written out when we're done. 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 bool which can be converted more directly. The last type conversion is for Enums. It 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 we 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 those are ignored. If there are missing keys in the config file MissingFields will be set and we call WriteKeysToConfig() to actually write the current configuration out to the config file.

Writing keys to the Config file
The WriteKeysToConfig() is used to write out 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.

The logistics of this method are a little different than the read method, primarily because .NET doesn't provide an interface to write value back to the Config file. ConfigurationSettings.AppSettings is a read only interface. So in order to write out data we'll have to do this the hard way by using XML directly. In the WriteKeysToConfig method shown in Listing 6 I use the XmlDocument class to read the Config file and update the structure.

Listing 6 - Writing the current configuration into the Configuration File

public void WriteKeysToConfig(string Filename) 
{
      // *** Load the config file into DOM parser
      XmlDocument Dom = new XmlDocument();
      Dom.Load(Filename);
      // *** Parse through each of hte properties of the properties
      Type typeWebConfig = this.GetType();
      MemberInfo[] Fields = typeWebConfig.GetMembers();
      foreach(MemberInfo Field in Fields) 
      {
            // *** If we can't find the key - write it out to the document
            string Value = null;
            if (Field.MemberType == MemberTypes.Field)
                  Value = ((FieldInfo) Field).GetValue(this).ToString();
            else if (Field.MemberType == MemberTypes.Property)
                  Value = ((PropertyInfo) Field).GetValue(this,null).ToString();
            else 
                  continue; // not a property or field
            XmlNode Node = Dom.SelectSingleNode(
                             @"/configuration/appSettings/add[@key='" + Field.Name + "']");
            if (Node == null) 
            {
                  // *** Create the node and attributes and write it
                  Node = Dom.CreateNode(XmlNodeType.Element,"add",null);
                  XmlAttribute Attr2 =Dom.CreateAttribute("key");
                  Attr2.Value = Field.Name;
                  XmlAttribute Attr = Dom.CreateAttribute("value");
                  Attr.Value = Value;
                  Node.Attributes.Append(Attr2);
                  Node.Attributes.Append(Attr);
                  XmlNode Parent = Dom.SelectSingleNode(@"/configuration/appSettings");
                  if (Parent == null) 
                  {
                        XmlNode AppSettingsNode = 
                              Dom.CreateNode(XmlNodeType.Element,"appSettings",null);
                        Parent = Dom.DocumentElement.AppendChild(AppSettingsNode);
                  }
                  Parent.AppendChild(Node);
            }
            else 
            {
                  // *** just write the value into the attribute
                  Node.Attributes.GetNamedItem("value").Value =  Value;
            }
            string XML = Node.OuterXml;
      } // for each
      Dom.Save(Filename);
}


Like the read code we use the MemberInfo collection to loop through each of the fields in the object. But here we check against nodes that exist in the XML document using XPATH expressions. If a node is not found we create it 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. For example, Figure 1 shows a Web interface for some of the keys we were looking at earlier, that are in this case databound directly against the App.configuration object.

http://www.programmersheaven.com/articles/rickshaw/configurationclass/image001.gif Figure 1 - Because configuration settings live in an object it's easy to databind to the properties and bind to them in the user interface.

Any change made in the User Interface 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 7.

Listing 7 - Loading and Saving configuration settings from the front end

private void Page_Load(object sender, System.EventArgs e)
{
      this.config = App.configuration;
      if (!this.IsPostBack) 
            this.BindData(); // Controls bind to this.config
}
private void Save_Click(object sender, System.EventArgs e)
{
      // *** Bind the data back to this.config
      this.UnbindData();
      if (this.bindingErrors != null) 
      {
            this.lblErrorMessage.Text = this.bindingErrors.ToHtml();
            return;
      }
      // *** Data is bound to this.config which maps to this.configuration
      App.configuration.WriteKeysToConfig();
      this.lblErrorMessage.Text = "Settings saved...";
}


Because we have strong types and an object instance, the databinding can occur directly against the properties of our Configuration object and data can directly be bound back. (Note: This example uses databinding classes described in a previous article: Implementing two-way Data Binding in ASP.Net). All you need to do is write out the data with WriteKeyToConfig() when you're done. It's that simple: the hardest part is creating the User interface.

In Windows Forms it's even easier if you use Properties (not fields as I've done here in the examples so far) 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 but remember 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 as well as 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 esential, 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.

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().

Both Read- and WriteKeysToConfig methods 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("ConnectionString,MailServerPassword","SuperSecret");
      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.

Play it again - and again...

A lot of talk about something that's pretty simple to use in the end, but I hope you find this configuration class useful - I know I do. As so many small things, a reusable tool like this makes life a lot easier especially when the next project rolls around and you get to reuse this functionality again.

As always if you have any questions or comments you can contact me on our message board at:

http://www.west-wind.com/wwThreads/Default.asp?Forum=White+Papers

About the Author

This article was written by By Rick Strahl from www.west-wind.com.
He can be contacted by email at rstrahl@west-wind.com





corner
© 1996-2008 CommunityHeaven LLC. All rights reserved. Reproduction in whole or in part, in any form or medium without express written permission is prohibited.
Violators of this policy may be subject to legal action. Please read our Terms Of Use and Privacy Statement for more information.
North American business development: Nicolai Wadstrom. Publisher: Lars Hagelin.
Resource Listings