C# Dynamic XElement wrapper

Xml manipulation is a common programming task but traditionally required significant coding. LINQ to Xml simplifies it. This article attempts a simpler wrapper using C# 4.0 dynamic programming techniques.

There are many tasks in our programming life where we have to deal with dynamic and evolving data structures. We often need to traverse them. One such case is Xml data. They can have arbitrary structures with varying number of element or attributes. The erstwhile Xml DOM API provided by .NET (XmlDocument or XmlReader classes) provided an extensive API to traverse, manipulate or query them. However, the amount of code we had to write for them was often prohibitive.

LINQ to Xml went a step ahead in providing a more concise API. For example, consider the following Xml structure:

<Director Name="Indranil" RevenueTarget="10000">
  <Department>Trading</Department>
  <Phone>18001112345</Phone>
  <Address>
    <Street>Diamond Enclave</Street>
    <City>Kolkata</City>
    <State>West Bengal</State>
    <Country>India</Country>
  </Address>
  <ReportingManagers>
    <Manager>John</Manager>
    <Manager>Steve</Manager>
  </ReportingManagers>
</Director>

This is how it can be created using LINQ to Xml:

var element = new XElement("Director",
                 new XAttribute("Name", "Indranil"),
                 new XAttribute("RevenueTarget", 10000),
                 new XElement("Department", "Trading"),
                 new XElement("Phone", 18001112345),
                 new XElement("Address",
                     new XElement("Street", "Diamond Enclave"),
                     new XElement("City", "Kolkata"),
                     new XElement("State", "West Bengal"),
                     new XElement("Country", "India"),
                     new XElement("ReportingManagers",
                         new XElement("Manager", "John"),
                         new XElement("Manager", "Steve")
                        )
                    )
                );

This is still a better syntax than the traditional XmlDocument API. However, it still requires a lot of new XElement or  new XAttribute statements.
What if we could add or update elements using syntax like this?

director.Department = “Trading”;

Or maybe, we could add attributes like this:

director[“Name”] = “Indranil”;

The problem here is, the elements and attributes are not known in advance and could possibly be anything. It’s not possible to define classes with fixed properties for Xml. Is it? Let’s modify that earlier statement. It can be possible to define a class with properties to create an xml tree and the properties can be dynamic.

The C# 4.0 dynamic programming API and the class DynamicObject from the .NET dynamic library allow us to do so. We’ve seen in an earlier FAQ, how to override the TryGetMember method of this class to provide customized behavior for member access. There are other methods of this class that be overridden to customize the behavior for setting a member value, indexer value, conversion etc.
Let’s try and build a wrapper around XElement class which enables us to add elements and attributes on the fly without explicitly using the LINQ to Xml API.
Here is the skeletal code for the class with a few constructors:

namespace Dynamic.Wrapper.Xml
{
    public class DynamicXElement : DynamicObject
    {
         private XElement _element;

        public DynamicXElement()
        {            
        }

        public DynamicXElement(string name)
            : this(new XElement(name))
        {
        }

        public DynamicXElement(XElement element)
        {
            _element = element;
        }        
    }
}

As we can see, there is  default no-argument constructor and a couple more that take the element name and a XElement respectively. The default constructor is used for a different purpose to be seen later. The other two instantiate a XElement and initialize a private member variable _element respectively. This XElement is really our backing store (i.e. all elements/attributes that are added or updated, get in here).
To be able to add or update an element using property syntax, we need to override the TrySetMember method. Here is how it looks like:

public override bool TrySetMember(SetMemberBinder binder, object value)                              
        {
            var node = _element.Element(binder.Name);

             if (node != null)
                 node.SetValue(value);
             else
            {
                 if (value is DynamicXElement)
                     _element.Add(new XElement(binder.Name));
                 else
                     _element.Add(new XElement(binder.Name, value));
            }

            return true;
        }

The binder parameter passes the name of the member we try to set. We use that to find a child element (of the private XElement member) by that name. If it’s found, then its value is updated with the value parameter. If not, then we need to add a new child by that name. for this case, there are two options. The code checks if the set value is of type DynamicXElement (this means that we want to add a container element) as a child the default constructor gets called here). In that case, we just create a blank container element. If not, we just add a child with the specified name and value. The method needs to return true if the set operation is successful (else a runtime exception will be thrown). With this in place, we can write statements like the following:

dynamic director = new DynamicXElement("Director"); //Creates the root element
director.Department = "Trading"; //This adds a department element with a value of "Trading" to the root
director.Department = "Purchase"; //This updates the Department element's value to "Purchase"

Now what if we need to access such an element using the same property syntax? For that we need to customize member access operation as follows:

public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            var node = _element.Element(binder.Name);
             if (node != null)
            {
                result = new DynamicXElement(node);
                 return true;
            }
            else
            {
                result = null;
                 return false;
            }
        }

Here too, we try to find a child having a name equal to the supplied binder.Name value. If it’s found, we return the child wrapped in a DynamicXElement object. Why? This is because, we want similar dynamic operations on the child as well so that we can write statements as shown below:

director.Address.State = "West Bengal";
// Here, director.Address invokes the get operation and the rest of the
// statement invokes the set operation
  
We might even need to assign element values to string variables such as the following statement:

string country = director.Address.Country;

But Country is of type DynamicXElement (thanks to our implementation of TryGetMember above) and we need a string back. The answer is to override TryConvert method as shown below:

public override bool TryConvert(ConvertBinder binder, out object result)
        {
             if (binder.Type == typeof(string))
            {
                result = _element.Value;
                 return true;
            }
            else
            {
                result = null;
                 return false;
            }
        }

Here we just check if the destination type for conversion is string. If yes, we just return the value of the wrapped XElement.
Let’s focus now on attributes. Since elements can be added/updated using property syntax, we need a different syntax for attributes. Something like:

director["Name"] = "Indranil";

This should update an existing attribute and add a new one if it doesn’t exist. We override TrySetIndex for that:

public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
        {
             if (indexes.Length == 1 && indexes[0] is string)
            {
                var attributeName = (string)indexes[0];
                var attribute = _element.Attribute(attributeName);
                 if (attribute != null)
                     attribute.SetValue(value);
                 else
                     _element.Add(new XAttribute(attributeName, value));

                 return true;
            }
            else
                 return false;
        }

The logic is similar to that of elements, just that we add/update instances of XAttribute class. There’s one check needed on the supplied indexes array parameter. It should have a single element and that should be of type string (as one can access one attribute by name at a time and attribute names are strings). Though this requires us to use the indexer syntax, but still we save key strokes as compared to adding XAttribute nodes explicitly.
Similarly, to access attribute values like the following statement:

Console.WriteLine("Display Name attribute: {0}", director["Name"]);

We override TryGetIndex:

public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
        {
            result = null;
            bool succeeded = false;

             if (indexes.Length == 1 && indexes[0] is string)
            {
                var attributeName = (string)indexes[0];
                var attribute = _element.Attribute(attributeName);
                 if (attribute != null)
                {
                    result = attribute.Value;
                    succeeded = true;
                }
            }

            return succeeded;
        }

What if we want to add multiple elements with the same name (collection of nodes)? Our property syntax works for single elements and allows only one with a particular name. to support collection of nodes, we provide the following Add method:
public void Add<T>(string name, T value)
         {
             if (value is DynamicXElement)
                 _element.Add(new XElement(name));
             else
                 _element.Add(new XElement(name, value));
        }

This is how we can use it:

director.ReportingManagers = new DynamicXElement();
director.ReportingManagers.Add("Manager", "John"); //First argument is the name of the element
director.ReportingManagers.Add("Manager", "Steve");

We override the ToString method to delegate the call to XElement.ToString method which allows us to dump the Xml as string.

public override string ToString()
        {
            return _element.ToString();
        }

This is by no means, a comprehensive API for Xml manipulation/traversal, but can be extended to add more features.


By Indranil Chatterjee   Popularity  (6661 Views)