Contents

Introduction

With the introduction of the .NET platform, Microsoft advices us developers to use XML files to configure our applications. Personally, I prefer this approach over the Windows registry or database as it perfectly supports the XCopy deployment. However there is one serious drawback: if you mess up the XML file - by let's say a missing or invalid closing tag - your whole application will stop working because of an XML parser exception. To avoid such mistakes - and of course enhance the configuration experience for the end user - you should provide a graphical interface.

Windows forms come with the built-in feature of simple databinding, allowing you to bind properties of objects to your input controls so you don't have to code these endless assignments between controls and objects any longer. This is achieved by a BindingManager (derived from BindingManagerBase) with two implementations coming out of the box: CurrencyManager (which is used for complex binding and binds ILists, IListSources and IBindingLists) and PropertyManager (which is used for simple binding and binds object properties). None of those is suitable to bind a configuration file to form controls.

And then there is the famous XmlDataDocument class to synchronize XML documents with datasets (which can be bound by the CurrencyManager) but creating an instance of a XmlDataDocument only works with kind of "tabular" XML.

This article intends to:

explain and show the approach of generating objects at runtime to wrap XML files in objects (XmlWrapper)

provide a component to enable databinding with the newly generated objects (XmlBindingManager)

provide design time support so you can actually use it in a plug & play fashion (PropertyExplorer)

This article does not intend to implement an XML editor where you can add or delete nodes. It is thought of as a convenient way to edit configuration data.

How to read the code

To help you understand the code (especially the scope of methods and variables), here are the relevant coding conventions I follow:

private fields start with a "_" prefix followed by lowercase

private methods start with lowercase

public or protected methods and properties (there are no non-private fields) start with uppercase

For clarity's sake, I omitted helper functions in this article where I thought that the implementation is rather trivial and the method name tells the story anyway (which a method name should always do ;-)). You can look them up in the source code though.

Design alternatives

There are two possible ways to achieve an automated databinding between WinForms controls and XML elements and attributes - at least these are the two that came to my mind:

Implementing your own BindingManager deriving from BindingManagerBase

Reusing the PropertyManager with a strongly typed XmlDocument

We will follow the second alternative - mainly because I think that we could use a strongly typed XmlDocument in a variety of scenarios while the use of a custom BindingManager would be very limited.

A strongly typed XmlDocument

By strongly typed XmlDocument, I refer to an object (representing the root node of the XML) which exposes all attributes and elements through properties. Let me just illustrate this by the following example:

Sample app.config (or web.config)

<configuration><appSettings><addkey="SomeSetting"value="This is the value of SomeSetting"/></appSettings></configuration>

A strongly typed XmlDocument should expose the value for "SomeSetting" by this code:

Console.WriteLine(Configuration.AppSettings.SomeSetting.Value);

This means we will have to define types at runtime to wrap the underlying XML.

Creating classes on-the-fly

There are two common ways to create types at runtime:

Using the System.CodeDom namespace

Emitting MSIL code

As advanced as it sounds to write MSIL code, I still prefer this option as using CodeDom is a very verbose activity (have you ever tried to write a type like this?). Also, I figured it won't be that much MSIL anyway.

The XmlWrapper

Accessing the values of elements and attributes of an XML document is actually straight forward when using XPath syntax. I won't go into detail about XPath and its possibilities as I assume you have at least heard of it.

As the XmlWrapper will wrap an XmlDocument (actually it wraps an XmlNode to allow partially wrapping), we start out with an overloaded constructor:

public XmlWrapper(XmlNode node)
{
_node = node;
Initialize();
}

To avoid writing too much MSIL and to be able to change the access implementation in the rather intuitive C# environment, we then write a generic getter and setter method which both takes an XPath expression as an argument and have to be public because they are called by the generated object properties:

The next step is to actually parse the XML and create the classes. Obviously, we will need to apply a recursive algorithm to step through the child nodes. We start out with initializing the assembly, defining a namespace (which will be the namespace of the XmlWrapper) and so on:

We first get the name for this new type including the correct (nested) namespace. Then we get an instance of TypeBuilder which will emit the new type, define a field _bindingManager to store a reference to the XmlWrapper and define the constructor. We then enter the recursive loop which will parse the node. Finally, we initialize all non string fields, create the type and store an instance of it in the private field _boundObject which represents the strongly typed XmlDocument.

As you can see, the ILGenerator for the constructor is finished only after the complete parsing and a call to initializeFields(). The reason is that we want to initialize all object properties with instances. The intitializeFields() method takes therefore a reference to the constructor builder as an argument.

privatevoid initializeFields(ArrayList childFields, ILGenerator ilCtor)
{
foreach(FieldBuilder field in childFields)
{
ConstructorInfo ctor =
field.FieldType.GetConstructor(new Type[] {GetType()});
//put yourself on the stack
ilCtor.Emit(OpCodes.Ldarg_0);
//define next statement as value for the field
ilCtor.Emit(OpCodes.Ldarg_1);
//get a new instance using the constructor taking an XmlWrapper
ilCtor.Emit(OpCodes.Newobj, ctor);
//store instance in the field
ilCtor.Emit(OpCodes.Stfld, field);
}
}

This MSIL would read as the following C# statement:

_someSetting = new SomeSetting(this);

In the recursive loop we follow the same pattern, defining a new type for every node and initializing the fields with new instances.

The interesting parts here are buildXPathForProperty() and buildProperty() which emit the call to the getter/setter with the defined XPath. While buildXPathForProperty() is rather straight forward, buildProperty() deserves a closer look. When defining a property, you actually define get/set methods (which is usually taken care of by the C# compiler). We follow the conventions of the compiler using "get_PropertyName" and "set_PropertyName" but you could name them any way you like. We also apply the method attributes and special name by convention but again this is not mandatory for making this code work. After defining those two methods, we assign them as the get method and set method of the property.

Additionally, the XmlWrapper offers some methods which encapsulate some reflection code. It is not really necessary, however, I would like to have reflection code rather hidden in a component than in a form or user control.

The XmlBindingManager

While the XmlWrapper encapsulates the underlying XmlNode, the XmlBindingManager provides the actual binding functionality. It does so by extending the properties of other controls by a XmlBinding property which maps a property of the control (usually the Text property) with a property of the XmlWrapper bound object.

As there are a lot of articles about design time support, I won't go into detail here. When using the XmlBindingManager at design time, you have to provide an XML file to bind to. However, as we just have seen that the underlying data source is actually an XmlNode, you could use this as well when implementing this component by code.

When parsing the XML node, the XmlWrapper will clash when finding two nodes with the same name within the same parent node because it would then try to generate two properties with the same name. As this will most likely happen when binding to an app.config file (<add key="SomeSetting" value="SomeValue" /><add key="AnotherSetting" value="AnotherValue" />), we will have to supply a resolver (although we could find a more sophisticated algorithm of generating property names). In this case, the resolver is simply a Hashtable (strongly typed for containing a key/tag pair). We will define by this that when finding, e.g. a <add ...> tag, the generator should use the key property for building the property name. The XmlBindingManager provides this through the TagSettings property.

The implementation of the XmlBindingManager is rather trivial as it only delegates to the parent BindingContext. All we have to do once we added the component to a form or user control is call the Initialize() and DataBind() methods. The Initialize() method sets up the XmlWrapper while the DataBind() method adds DataBinding to all the bound controls.

The PropertyExplorer

Last thing to do is to offer some design time support by means of the PropertyExplorer which makes it a two-click solution to bind any control to the underlying data source. It is a simple treeview control showing all properties of the generated wrapper class. It will show up when you want to set the XmlProperty of the XmlDataBinding setting in the property grid. It is implemented as a drop-down designer but there is commented code to implement it as a popup designer if you choose so.

If you have any problems using the design time features of these components, try to unload all add-ins of the Visual Studio. It took me some time to find out why the TagSettings has not been serialized correctly into code just to find out that my version of CodeSmith obviously was occasionally disturbing. Once I had unloaded it, everything worked just fine.

Using the code

Just compile the source, add the XmlBindingManager to your toolbox and drag an instance on your form or user control. Select the XML file you want to bind to, click on a control you want to bind, look up the XmlDataBinding property for this control (it's in the XML category) and select the node from the PropertyExplorer. Of course, you can change to a different XML file at runtime. All you have to do is:

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.