Eliminate a Tedious Task Through Creating Self Configuring Objects

Wednesday Dec 11th 2002 by Mark Strawmyer

Retrieving application settings is a pretty common task that falls under the category of another of those tedious and repetitive tasks that programmers have to deal with. Explore a way to eliminate this tedious programming task through a combination of technologies.

From CodeGuru's .NET Nuts & Boltscolumn.

The focus of this article will be on creating objects that can automatically configure themselves at the time they are used. This will involve combining several different technologies that have been discussed in past articles. The technologies involved will be Custom Attributes and Reflection.

It is a common practice to store application settings and configurations in a location where the application can retrieve modify it if necessary. It has been common practice to store such configuration information in text files with an extension of 'INI'. The 'INI' files take on a special format and can be read a variety of ways whether it is through a custom reader or other APIs. As the windows registry evolved it became commonplace to store values in the windows registry rather than 'INI' files. The registry offers additional benefits of security and in-memory caching that 'INI' files do not offer. Independent of the storage location, programmers frequently must write code to have initialize objects based on the configuration information.

This brings me to the focus of this column. Retrieving application settings is a pretty common task. It falls under the category of another of those tedious and repetitive tasks that programmers have to deal with. It frequently involves copying related code from another constructor and then modifying it as appropriate. It also leads to additional code that needs to be maintained to read and assign the value for each property. Whenever a property is added the appropriate code must also be added to set its value based on the configuration. Let's explore a way to eliminate this tedious programming task through a combination of technologies.

Creating an XML Configuration File

For the sake of this article we will store our configuration in an XML file. There are several reasons for this. The main reasons are that not everyone has access to their registry or registry editing tools, and I don't want to encourage people to go messing around with their registry where they could potentially do harm to their machine.

Here is the XML format of the configuration file we'll use for this example. Each configuration will consist of an element representing the parameter with a name and value pair attributes describing the parameter. The file should be named config.xml and be located in the \bin\Debug folder in the project directory.

Sample XML Configuration File

<Configuration>   <Parameter Name="Parameter1" Value="Value1" />   <Parameter Name="Parameter2" Value="Value2" /><Parameter Name="Parameter3" Value="Value3" /><Parameter Name="Parameter4" Value="Value4" /></Configuration>

Creating a Configurator Object to Read the XML Configuration

Next we will need an object to interact with in order to retrieve and update the contents of our XML configuration file. The object will load the XML document and provide methods to access and update the values in our XML parameter structure.

Sample Configuration Object

using System;using System.Xml;/// <remarks>/// Object to control configuration of an application.  Will /// read settings from an XML file./// </remarks>public class Configurator{  // Configuration file name.  The name is built using the   // directory of the application.  When in development this  // is likely the \bin\Debug directory in the project folder.  private readonly string _FILENAME =    System.AppDomain.CurrentDomain.BaseDirectory + "config.xml";        // XML containing configuration settings  private XmlDocument _ConfigFile = null;  /// <summary>  /// Constructor  /// </summary>  public Configurator()  {this._ConfigFile = new XmlDocument();   XmlTextReader reader = null;   try   {     // Load the XML file     reader = new XmlTextReader(this._FILENAME);     reader.Read();     this._ConfigFile.Load(reader);   }   catch   {     // Unable to load the XML file  verify file path     System.Diagnostics.Debug.WriteLine("Unable to open file: " + this._FILENAME);     this._ConfigFile = null;   }   finally   {     reader.Close();   }  }  /// <summary>  /// Get the value for the desired setting.  /// </summary>  /// <param name="SettingName">Name of setting value to   ///             retrieve. </param>  /// <returns>String containing setting value. </returns>  public string GetSetting(string SettingName)  {string returnValue = "";      // Value to return            // Retrieve the appropriate Parameter node from the XML   XmlNode node =      this._ConfigFile.DocumentElement.SelectSingleNode(      "descendant::Parameter[@Name='" + SettingName + "']");   if( node != null )   {     // Retrieve the setting value from the Value attribute     returnValue =          node.Attributes.GetNamedItem("Value").InnerText;   }   return returnValue;  }  /// <summary>  /// Save a new value for the desired setting.  If the setting   /// does not already exist then it will be automatically created.  /// </summary>  /// <param name="SettingName">Name of the setting. </param>  /// <param name="SettingValue">New setting value. </param>  public void SaveSetting(string SettingName, string SettingValue)  {// Get the value from the XML   XmlNode node =      this._ConfigFile.DocumentElement.SelectSingleNode(      "descendant::Parameter[@Name='" + SettingName + "']");   if( node != null )   {        // Set the new value of the Value attribute     node.Attributes.GetNamedItem("Value").InnerText = SettingValue;   }   else   {     // Parameter does not exist so create it     XmlElement setting =            this._ConfigFile.CreateElement("Parameter");     setting.SetAttribute("Name", SettingName);     setting.SetAttribute("Value", SettingValue);     this._ConfigFile.DocumentElement.AppendChild(setting);   }   this._ConfigFile.Save(this._FILENAME);  }}

Testing the Configurator

You should properly test out this object before going any further. You should verify parameters are correctly read and updated. The object can be tested by executing the following code and verifying the contents of the file:

Configurator config = new Configurator();string setting1 = config.GetSetting("Parameter1");config.SaveSetting("Parameter2", "testing");config.SaveSetting("Testing", "testing");

The above test against the configuration file previously defined should result in:

<Configuration>  <Parameter Name="Parameter1" Value="Value1" />  <Parameter Name="Parameter2" Value="testing" />  <Parameter Name="Parameter3" Value="Value3" />  <Parameter Name="Parameter4" Value="Value4" />  <Parameter Name="Testing" Value="testing" /></Configuration>

Creating a Custom Attribute

Custom attributes are classes that are used to provide additional descriptive information about properties, methods, etc. used in your classes. The information from the attribute can be obtained programmatically using our friend Reflection.

A custom attribute begins with the AttributeUsage attribute, which defines the way your attribute class can be used. Since a custom attribute uses an attribute it provides one example of how attributes can be used just by creating a custom attribute. There are three parameters that can be applied to attributes. The AttributeTargets defines where the attribute can be used, for example, properties, methods, classes, etc. The Inherited parameter defines whether or not classes that are derived from classes that use your attribute inherit your attribute too. The AllowMultiple parameter defines whether or not multiple instances of your parameter can exist on an item.

Sample Custom Attribute

Here is an example of a custom attribute created to map an object property to a configuration file entry. The attribute consists of a FieldName that contains the name of the configuration file parameter and an indicator if the object is included in the file or not. The indicator is included in so can easily assign or stop assigning a property from the configuration file.

using System;/// <remarks>/// Attribute used to indicate if a property should be configured /// by the Configurator object.  It is valid only on Properties and/// only one attribute is allowed per property./// </remarks>[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]public class ConfiguratorFieldAttribute : Attribute{  // Internal Name value  private string _FieldName = "";  /// <value>Name of the data field</value>  public string FieldName  {get { return this._FieldName; }   set { this._FieldName = value; }  }  // Internal InConfiguration value  private bool _InConfiguration = false;  /// <value>Indicator if the data field is in   ///        configuration file</value>  public bool InConfiguration  {   get { return this._InConfiguration; }   set { this._InConfiguration = value; }  }  /// <summary>  /// Constructor  /// </summary>  /// <param name="FieldName">Name of the data field</param>  /// <param name="InConfiguration">Indicator if data field   ///              in config</param>  public ConfiguratorFieldAttribute(string FieldName,                                     bool InConfiguration)  {   this._FieldName = FieldName;   this._InConfiguration = InConfiguration;  }}

Sample Object Using ConfiguratorField Custom Attribute

The follow object demonstrates the use of the custom attribute. For the example we used a property name of "Property#" and map the TestObject property to the appropriate parameter through the FieldName property of the custom attribute. It is important to note the quirky implementation detail where the "Attribute" portion of the object name that is required in the object definition, but is left off when used.

using System;/// <remarks>/// TestObject to read configuration from file./// </remarks>public class TestObject{  private string _Property1 = "";  [ConfiguratorField("Parameter1", true)]  public string Property1  {   get { return this._Property1; }   set { this._Property1 = value; }  }  private string _Property2 = "";  [ConfiguratorField("Parameter2", true)]  public string Property2  {   get { return this._Property2; }   set { this._Property2 = value; }  }  private string _Property3 = "";  [ConfiguratorField("Parameter3", true)]  public string Property3  {   get { return this._Property3; }   set { this._Property3 = value; }  }}

Using Reflection to Automatically Retrieve Configurations

Now we need to add a method to our Configurator object that will load the appropriate properties of an object with the correct values from the configuration file. This is done using objects in the System.Reflection namespace.

Sample Method To Use Custom Attribute To Set Object Property

The following method should be added to the Configurator object previously created.

/// <summary>/// Populate the given object with the configuration /// information using reflection./// </summary>/// <param name="ObjectToConfig">Object to configure</param>public void ConfigureObject(object ObjectToConfig){  ConfiguratorFieldAttribute attr; // Custom                                   //  ConfigurationFieldAttribute  object[] attributes;         // Attributes assigned to the object  PropertyInfo property;      // Object property  // List of object properties  PropertyInfo[] itemTypeProps =                   ObjectToConfig.GetType().GetProperties();  // Get a list of the ConfiguratorField attributes for the object  for( int i = 0; i < itemTypeProps.Length; i++ )  {   // Get the current ConfiguratorField attribute   property = itemTypeProps [i];   attributes = property.GetCustomAttributes(typeof(ConfiguratorFieldAttribute), true);      // Verify the ConfiguratorField attribute was foundif( attributes != null && attributes.Length == 1 )    {       // Determine if the field should be included based on the     // InConfiguration indicator     attr = (ConfiguratorFieldAttribute) attributes[0];     if( attr.InConfiguration == true && property != null )     {      property.SetValue(ObjectToConfig, this.GetSetting(attr.FieldName), null);     }   }  }}

Testing the Configurator

The configurator is now complete and should be ready for use. It can be tested against the TestObject created earlier as follows:

TestObject o = new SelfConfigure.TestObject();config.ConfigureObject(o);System.Diagnostics.Debug.WriteLine("Property1: " + o.Property1);System.Diagnostics.Debug.WriteLine("Property2: " + o.Property2);System.Diagnostics.Debug.WriteLine("Property3: " + o.Property3);System.Diagnostics.Debug.WriteLine("Property4: " + o.Property4);

The above test against the configuration file previously defined and tested should result in output similar to the following:

Property1: Value1Property2: testingProperty3: Property4: Value4

Possible Enhancements

Now we have an automated way for objects to configure themselves. There are all sorts of enhancements that could make this even more valuable. Here are some ideas that you can consider for yourself.

  • Add additional error checking and exception handling to properly react to non-existent parameters or errors that may occur during the process.

  • Move the call to the ConfigureObject method into the constructor of each object so that it is automatically configured when the object is instantiated.

  • Change the Configurator object to have static methods so that it does not have to be instantiated to be used.

  • Data such as passwords cannot securely be stored in a configuration file or the registry because they could be accessed and read. Add functionality to encrypt and decrypt settings as appropriate. Modify the configuration file Parameter elements to include an additional attribute indicating whether the value is encrypted or not. When getting or setting the value of a parameter, first read the encryption attribute. If the encryption attribute indicates the value is encrypted, then encrypt or decrypt the data according to the action being performed.

  • Change the storage location from an XML file to the appropriate location. This could consist of a database, registry settings, or any other desired location.

  • When creating objects for use in ASP.NET, move the storage location from the custom XML file to the Web.config file of the ASP.NET project.

  • Enhance the DataReflector example from the article "Taking Advantage of Custom Attributes and Reflection to Eliminate a Tedious Task" to read the database settings from a configuration location instead of being hard coded.

Future Columns

I don't have a specific topic yet identified for the next column. I have had several email discussions with folks, such as the one that generated this article, which may yield a topic of the next article. If you have something in particular that you would like to see explained please email me at mstrawmyer@crowechizek.com

About the Author

Mark Strawmyer, MCSD, MCSE (NT4/W2K), MCDBA is a Senior Architect of .NET applications for large and mid-size organizations. Mark is a technology leader with Crowe Chizek in Indianapolis, Indiana. He specializes in architecture, design and development of Microsoft-based solutions. You can reach Mark at mstrawmyer@crowechizek.com.

# # #

Mobile Site | Full Site
Copyright 2017 © QuinStreet Inc. All Rights Reserved