In Depth Banner
Skip Navigation Links

Easy Configuration

A constantly recurring requirement is a simple yet comprehensive method of configuring an object at run-time. For example, a form, control or even a simple object may be adjusted by the user or subject to user preferences that cannot be set at design-time. One method of course is to build a configuration system into all of the objects that need it but this would mean that all the objects may have to derive from a specific object or implement a standard interface. These methods present subtle problems however. In the case of objects derived from a standard configurable object, the inheritance tree can be at best complex and at worst, downright annoying due to the lack of multiple inheritance in C# and VB.NET. Implementing an interface can require that the same code be reproduced several times in several places which is ok but can be a maintenance problem if some aspect of the configuration code needs to change.

The problem.

Before looking at the solution, consider the problem of creating a configuration system that will allow you to save and restore properties of your application and it's member classes. The Form class has some 850-odd member properties and fields that are private, protected and public. Deciding which ones should be saved and which ones left to the default behaviour is obviously a brain teaser. That's just for the standard form. A form constructed with the designer usually has several, if not a whole lot of, private members that represent the controls used in the form. Buttons, CheckBoxes, TextBoxes all have their own different properties so how is it possible to create a single, simple configuration system that can remember the settings of important items from instance to instance of the code.

Given any two Windows Forms applications, the data for preserving the state of forms and their components are so different that it may seem impossible to predict how two different applications might keep a record of themselves unless we can give the configuration serialization system a hint about what to keep and what to leave for the defaults.

The answer to this dilemma is to use the .NET system to it's best advantage and enable configuration through reflection. Using this method, any object that exposes properties can be provided with a reliable and re-usable configuration system. Of course, the configuration data stored can be of any format but by far one of the easiest to understand, edit and store is XML. This article therefore proposes a universal configuration system that has all these attributes.

Big hints

A configuration system that saves everything is almost as useless as no configuration system at-all. We must therefore be able to hint to the system exactly what we feel to be important and just ignore the rest. Think about how easy it would be if we could tell an application "Next time you run, I want you to remember the position and size of the main form and restore the old settings for me." Think also about the idea of wishing to save the content of a TextBox that is a child of the form and restore that too. You have to specify which properties and indeed, which properties of private fields of a running class should be accessed.

For this task it's possible to rely on the wonderful Attribute system of .NET to provide us with hints for what should and should not be maintained from instance to instance of the application. Custom attributes are ridiculously simple to write and enable us to tag classes, fields, properties and many other program entities with metadata that can be read by the reflection system at runtime. Furthermore, the fact that a field is private is no barrier to obtaining the data from it or altering it's contents using reflection.

Two attributes have been created for this article. The ConfigurableAttribute is applicable to fields and enables you to specify that a field such as the private TextBox member of a form should be considered for config serialization. The second attribute, ConfigurablePropertyAttribute, may be applied to a class in order to specify which of its properties should be saved or to a field that has already got a ConfigurableAttribute to specify which of the field's properties should be saved. An example of using these attributes is seen in this listing.

The attributes that provide this metadata are shown in this listing.

Upon reflection

When a class and it's controls have been suitably tagged we can use this metadata to get the information required at runtime. Including, if necessary, the contents or properties of private fields.

The process is fairly convoluted and goes like this...

The configuration system has a file name and a section name. This enables you to store more than one section in a single file. To write the configuration the only data needed is the object who's configuration should be saved. Configuration discovery is done in two phases. In the first phase, the fields of the class are checked to see if they have any interesting properties. To accomplish this, reflection is used to obtain a list of all public and private members from the object. This list is presented in the form of a MemberInfo array in response to the GetMembers method exposed by the System.Type object. Each member can be checked to see if it's a field, components in a form will be fields, and if the field has been tagged with a ConfigurableAttribute. If these conditions are satisfied we can check for the properties that are interesting by checking again for the ConfigurablePropertyAttributes that may be attached to the field. Each of these attributes are used to discover and read the contents of the properties which can be written to the XML file. The second phase revisits the object that was passed in to discover what properties are to be saved by looking for ConfigurablePropertyAttributes that may be attached to the class. Each of these are read and saved in the XML file again. This listing shows the writing process.

Reading the configuration back from the XML file is very similar. The discovery process outlined above is used to determine which items are of interest and the value stored in the XML file is extracted by constructing an XPath which navigates through the XML to the value desired. Reflection is used to place the stored value into the destination property. The reading process is shown in this listing

In both writing and reading of properties the type-converter is used to convert between the value of the property and it's text representation. It is therefore possible to extend this system to any custom property by providing a suitable TypeConverter for it.

Automating the configuration

An application that needs to save it's configuration needs only to write the configuration when it closes and read it in when it loads. Furthermore, because the configuration is in XML you can fiddle with it using a simple text editor, perhaps to create an initial configuration of your choice.

The code page contains the EasyConfig component source and an example application that maintains it's position and size on screen plus some settings in it's controls.

You can find the Source Code files here.

Return to the main index.

Copyright © Bob Powell 2003-2009. All rights reserved