http://www.developer.com/net/csharp/article.php/1555451/Eliminate-a-Tedious-Task-Through-Creating-Self-Configuring-Objects.htm
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. 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 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 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: The above test against the configuration file previously defined should result in: 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. 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. 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. 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. The following method should be added to the Configurator object previously created. The configurator is now complete and should be ready for use. It can be tested against the TestObject created earlier as follows: The above test against the configuration file previously defined and tested should result in output similar to the following: 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. 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 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. # # #
Eliminate a Tedious Task Through Creating Self Configuring Objects
December 11, 2002
Creating an 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
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
Configurator config = new Configurator();string setting1 = config.GetSetting("Parameter1");config.SaveSetting("Parameter2", "testing");config.SaveSetting("Testing", "testing");<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
Sample Custom Attribute
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
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
Sample Method To Use Custom Attribute To Set Object Property
/// <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
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);Property1: Value1Property2: testingProperty3: Property4: Value4
Possible Enhancements
Future Columns
About the Author