In part 1 of this article, I showed you how to create a fluent parser for XML documents as a dynamic type in C# 4. With that dynamic class, you can use the dot (.) and index ([]) operators to traverse any XML document with a very natural syntax. Being able to read XML fluently is great. But to be really useful, our dynamic class should allow the programmer to modify the XML document fluently as well. To do this, we'll need to modify the behavior of the overridden TryGetMember method to create missing XElements whenever they are referenced by the code. Here's the modified code for the TryGetMember method:

public override bool TryGetMember( GetMemberBinder binder, out object result)
{
 result = null;
 
 /* handle the Value and Count special cases */
 if (binder.Name == "Value")
 result = _elements[0].Value;
 else if (binder.Name == "Count")
 result = _elements.Count;
 else
 {
 /* try to find a named attribute first */
 var attr = _elements[0].Attribute( XName.Get(binder.Name));
 if (attr != null)
 {
 /* if a named attribute was found, return that NON-dynamic object */
 result = attr;
 }
 else
 {
 /* find the named descendants */
 var items = _elements.Descendants( XName.Get(binder.Name));
 if (items != null && items.Count() > 0)
 {
 /* prepare a new dynamic object with the list of found descendants */
 result = new DynamicXml(items);
 }
 }
 }
 if (result == null)
 {
 /* not found, create a new element here */
 _elements[0].AddFirst( new XElement( binder.Name ) );
 result = new DynamicXml( _elements[0].Descendants().First() );
 }
 return true;
}

Notice near the bottom of the TryGetMember method that when the object named in the binder is not found, a new XElement is created and added as the first child of the current element. Then, a DynamicXml wrapper is created to continue the fluent chain to the next call as may be necessary. Since the specially handled "Value" member is used to get the value of a DynamicXml element, we also need to handle the "set" case for the times when the programmer attempts to assign a Value to an element. To do that, we need to override the TrySetMember method in the DynamicXml class like this:

public override bool TrySetMember( SetMemberBinder binder, object value)
{
 if (binder.Name == "Value")
 {
 /* the Value property is the only one that may be modified.
 TryGetMember actually creates new XML elements in this implementation */
 _elements[0].Value = value.ToString();
 return true;
 }
 return false;
}

With these two changes in place, we can write code to modify the underlying XML document, too. In the following code that exercises both of the code changes, notice that the first author of the second book has no middle name element in the source XML document. With the changes we've made to TryGetMember and TrySetMember, however, this isn't a problem at all. Look at the code that assigns the middle name value of "Lisa" to that author below.

var xml = "" +
 "" +
 "" +
 "" +
 "" +
 "" +
 "Mortimer" +
 "Q." +
 "Snerdly" +
 "" +
 "" +
 "" +
 "" +
 "" +
 "" +
 "" +
 "" +
 "" +
 "" +
 "Trudy" +
 "Freefall" +
 "" +
 "" +
 "" +
 "" +
 "" +
 "Bernard" +
 "M." +
 "Fallson" +
 "" +
 "" +
 "" +
 "" +
 "" +
"";
 
dynamic dx = new DynamicXml(xml);
dx.book[1].authors.author[0].name.middle.Value = "Lisa";
 
foreach (dynamic b in dx.book)
{
 Console.WriteLine("----- Begin Book -----");
 Console.WriteLine("Price='{0}'", b.price.Value);
 Console.WriteLine("Title='{0}'", b.title.Value);
 Console.WriteLine("AuthorCount='{0}'", b.authors.author.Count);
 foreach (dynamic a in b.authors.author)
 {
 Console.WriteLine("---- Begin Author ----");
 Console.WriteLine("EmailAddress='{0}'", a.email.address.Value);
 Console.WriteLine("FirstName='{0}'", a.name.first.Value);
 Console.WriteLine("MiddleName='{0}'", a.name.middle.Value);
 Console.WriteLine("LastName='{0}'", a.name.last.Value);
 Console.WriteLine("----- End Author -----");
 }
 Console.WriteLine("------ End Book ------");
}

When information about the first author of the second book is dumped to the console, we see that it contains the middle name that was assigned in the fluent mutation of the XML document:

---- Begin Author ----
EmailAddress='tfreefall@jump.com'
FirstName='Trudy'
MiddleName='Lisa'
LastName='Freefall'
----- End Author -----

Nice and easy, right? That's enough for this installment. In the next article in this series, I'll add some error handling code and deal with "the XML attribute problem" as it will come to be known. Enjoy.