Dynamic XML Reader with C# and .Net 4.0

Last week I was at a brilliant .net 4.0 training session lead by Richard Blewett of Developmentor (https://www.develop.com/)

Along with the new features of .net 4.0 (including the long awaited Tuple classes, BigInteger (an unbounded int), extra Code Access Security stuff and a brilliant new feature Code Contracts) is the dynamic keyword, which allows better COM interop, and while probably won’t make an appearance in everyday programming, will help with interop with dynamic languages such as Python and Ruby (or Iron Python & Iron Ruby).

Given the XML doc (order.xml):

 <?xml version="1.0" encoding="utf-8" ?>
 <order orderId="123">
   <customer>
     <name>Wile E Coyote</name>
     <address>The Desert</address>
   </customer>
   <orderItem>
     <product>Rocket Powered Rollerskates</product>
     <quantity>1</quantity>
     <supplier>Acme Inc</supplier>
   </orderItem>
 </order>

we could set up an XmlDocument or XDocument via LINQ to read this, but this could cause some complex constructs within your code, and therefore make it less readable.  Ideally we would like to say to get the customer’s name, customer.name, treating the xml doc’s structure as a strongly typed object graph.

Using a simple class extending DynamicObject (a new class within .net 4.0 in System.Dynamic which allows TryGetMember methods to override and TryInvoke methods for method invocation to allow dynamic creation of a meta class at runtime based on data provided, all you need to specify is how to handle the method calls.

So a basic Dynamic XML reader would look like this:

 class DynamicXmlParser : DynamicObject
 {
     XElement element;
  
     public DynamicXmlParser(string filename)
     {
         element = XElement.Load(filename);
     }
  
     private DynamicXmlParser(XElement el)
     {
         element = el;
     }
  
     public override bool TryGetMember(GetMemberBinder binder, out object result)
     {
         if (element == null)
         {
             result = null;
             return false;
         }
  
         XElement sub = element.Element(binder.Name);
  
         if (sub == null)
         {
             result = null;
             return false;
         }
         else
         {
             result = new DynamicXmlParser(sub);
             return true;
         }
     }
  
     public override string ToString()
     {
         if (element != null)
         {
             return element.Value;
         }
         else
         {
             return string.Empty;
         }
     }
  
     public string this[string attr]
     {
         get
         {
             if (element == null)
             {
                 return string.Empty;
             }
  
             return element.Attribute(attr).Value;
         }
     }
 }

You will notice that we are still loading the XML doc under the covers as an XElement, but the most interesting points to note here are the override method:

 public override bool TryGetMember(GetMemberBinder binder, out object result) 

which looks to see firstly if an xml document has been loaded, and secondly looks at the binder.Name object passed in (which would be the invoked member’s name) and, if found, returns a new instance of the class pointing at the element requested.

Secondly:

 public string this[string attr]

which allows an index style access to attributes on the xml element.

We can now write statements such as:

 dynamic parser = new DynamicXmlParser(@".\order.xml");
  
 Console.WriteLine(parser.customer.name);

which will print out the customer’s name to the console and

 dynamic parser = new DynamicXmlParser(@".\order.xml");
  
 Console.WriteLine(parser["orderId"]);

which will print out the orderId attribute on the order.

This can also be extended to work with CSV files:

 class DynamicCSV : DynamicObject
 {
     List<string> columns;
     StreamReader sr;
     string[] currentLine;
  
     public DynamicCSV(string file)
     {
         sr = new StreamReader(file);
         string columnLine = sr.ReadLine();
  
         columns = columnLine.Split(',').ToList<string>();
     }
  
     public bool Read()
     {
         if (sr.EndOfStream)
         {
             return false;
         }
         else
         {
             currentLine = sr.ReadLine().Split(',');
             return true;
         }
     }
  
     public override bool TryGetMember(GetMemberBinder binder, out object result)
     {
         int index = columns.FindIndex(col => col == binder.Name);
  
         if (index == -1)
         {
             result = null;
             return false;
         }
  
         result = this.currentLine[index];
         return true;
     }
 }

which allows accessing of CSV files with a named header (this parser assumes the first line of the CSV is the name of the column).