Export (0) Print
Expand All
Expand Minimize

XPath Querying Over Objects with ObjectXPathNavigator

 

Steve Saxon
Dell Computer Corporation

March 2, 2003

Summary: Steve Saxon discusses the ObjectXPathNavigator, which provides the power and flexibility of the XML programming model without the overhead of having to serialize an entire object graph into an XmlDocument object. (21 printed pages)


Download the XML03172003_sample.exe file.

Requirements   This download requires that the Microsoft.NET Framework 1.0 be installed.
Editor's Note   This month's installment of Extreme XML is written by guest columnist Steve Saxon. Steve lives in Austin, Texas, and is a Technical Architect for Worldwide Content Publishing on Dell.com. He has been working closely with Microsoft XML products for several years, and led the team that put Dell's in-house XML-based content publishing system live using ASP.NET in May 2001 (before beta 2 was released!). Steve has been publishing Web content from XML since 1998 and even co-authored a chapter on the subject in the Sybex book Mastering XML.

Introduction

Since XML first came on the scene in the late 1990s, developers have been trying to find efficient ways to get their object models into XML so they can pass their data around or simply manipulate the data using mechanisms, such as XSLT. In the beginning, developers would write the data out directly, but this was very fiddly because you needed to be wary of the semantics of XML, and handle special cases such as when your data contained reserved characters such as "<" and "&".

The Microsoft .NET Framework gave us even simpler ways right out of the box. We can now let the System.Xml.XmlTextWriter class take care of the XML semantics. This class is fairly simple to use, but as the example below shows, can be verbose.

Using XmlTextWriter to output XML

XmlTextWriter writer = new XmlTextWriter( Console.Out );

writer.Formatting = Formatting.Indented;

writer.WriteStartElement( "sampleNode" );
writer.WriteAttributeString( "anAttribute", "Test" );
writer.WriteStartElement( "nestedNode" ); 
writer.WriteString( "XmlTextWriter is easy" );
writer.WriteEndElement();
writer.WriteEndElement();

Output

<sampleNode anAttribute="Test">
    <nestedNode>XmlTextWriter is easy</nestedNode>
</sampleNode>

If we didn't care about the exact presentation of the output, we could have the System.Xml.Serialization.XmlSerializer class serialize the entire object graph out in one method call. However, all of these mechanisms have one common flaw—there is no way to filter or query this output until the entire stream is loaded into an XmlDocument object, even if you are only interested in one or two nodes from the output.

In this article, we'll look at a way to use XPath queries and XSLT directly on your objects, without the need to serialize.

Why Query Directly?

You may be asking yourself why would you want to be able to use XPath directly on your objects? After all, if it's not that hard to serialize it out, then why not just do that, then read it back in again and query it.

Apart from the obvious performance implications of such a scheme, it is clumsy to have to code your application in that way because it means creating temporary XML documents (though you could create them in memory using System.IO.MemoryStream from the .NET Framework). As a result, most developers would most likely not considering doing such a thing, which I feel is a pity because there are a number of excellent opportunities that open up once you consider exposing your objects through XPath.

Complex Object Querying

The first and perhaps most obvious opportunity is complex object querying. By this I mean the idea of being able to write queries that touch several parts of the internal structure of the object in some complex, possibly arbitrary way.

For example, consider the code you might have to create if you had written an Order Form object and were given a requirement to "find me all the items in this order form that are due to be shipped to Texas." Writing longhand code to perform such lookups can be tedious as you have to drill down to the right business object collection, then enumerate through it trying to match the field. You might end up with something like:

ArrayList texasItems = new ArrayList();

foreach( LineItem item in this.Items )
{
    if( item.ShippingAddress.State.StateCode == "TX" )
    {
        texasItems.Add( item );
    }
}

Imagine if you get a new requirement to change this lookup to add "unless the shipping address matches one of the billing addresses." Suddenly your code gets much more complicated because it then has to perform a logical join to another part of your business object graph. This is the kind of thing that XPath is great for, but up until now there has been no way to directly use XPath on your objects.

The Client/Server Mismatch Problem

The second issue I feel XPath-enabling your objects helps with is where part of your application's internal data needs to be exposed to the outside world. Consider a scenario where two different teams have two applications that need to pass business objects between them. If you're lucky, it may be possible to share the same business object code or object model, in which case you can use mechanisms such as Web services or .NET Remoting to communicate between the two applications. However, what do you do in the case where the two applications have quite different internal object models?

If you've already decided that the two applications need to communicate through XML (so that you're not bound by binary protocols), then as described earlier, you could simply serialize out the objects from the first application and pass it to the second application. If the object graphs are different, you could pass the XML through an XSLT transform to coerce it into the format the target application expects. The downside of this approach comes where the second application only needs to be a fraction of the output from the client application. In this case, a lot of the data will be discarded, meaning you wasted a lot of time serializing data that was never used.

As I showed you earlier, you might choose to have the client application build up the XML piece by piece to get it directly into the format that the target application understands. But as we've seen, XmlTextWriter can be fairly tedious to use, and the resulting code is hard to read and maintain. Plus, you then need to make code changes whenever the client or the target application changes their object model.

In an ideal world, you would be able to leverage all the benefits of XSLT (the main one here being that you can maintain the client/target data mapping outside of your application's code). However, in doing so what you would ideally want is a way to have the XSLT transform operate directly over your objects for those cases where much of the client data will be ignored.

Introduction to XPathNavigator

If you've ever used MSXML, you will no doubt be familiar with the DOM or Document Object Model API that it provides for manipulating XML. When the .NET Framework was released it was no surprise that it included a DOM API set. What you might not have spotted is that Microsoft included a whole other way to manipulate XML—the XPathNavigator model. Unlike the node-based Document Object Model, XPathNavigator provides a read-only, tree-based cursor model for walking through an XML dataset. What you may not have realized is that XPathNavigator is not limited specifically to XML. With the appropriate implementation, you could use it to query the file system, the Windows® registry, an Active Directory® store, or any other kind of hierarchical data store.

So what about objects? They too have a kind of implicit hierarchy to them. You have the object you're starting from, and all of its properties are like attributes or child nodes in XML. If you've ever drilled into an object in the Watch window in Microsoft Visual Studio® .NET you'll know what I mean.

click for larger image

Figure 1. Microsoft Visual Studio .NET Watch window

Enter ObjectXPathNavigator

If we had a way to create an XPathNavigator for navigating over an object graph, we could perform XPath queries to let us write complex queries directly against our objects. You could also pass the XPathNavigator over the object directly into an XSLT transform without needing to serialize it first. The XSLT could then walk through your object hierarchy as it needed to, touching only the objects you referenced. This is exactly what my ObjectXPathNavigator does.

To show you how it works, I built a sample around a shopping basket that contains a number of items. All of the items are of type Book or PowerTool (such as a power drill). As all basket items have certain things in common, I made both share a base class called Item that provides properties such as Description, Quantity, and UnitPrice. I also added an array of strings to categorize the item and allow us to see how collections come out.

public class Item
{
    private string      m_desc;
    private int         m_quantity;
    private decimal     m_unitPrice;
    private string[]    m_cat;

    protected Item( string desc, int quantity, decimal unitPrice,
                    string[] cat )
    {
        m_desc        = desc;
        m_quantity    = quantity;
        m_unitPrice    = unitPrice;
        m_cat        = cat;
    }

    public string Description
    {
        get { return m_desc; }
    }

    public int Quantity
    {
        get { return m_quantity; }
    }

    public decimal UnitPrice
    {
        get { return m_unitPrice; }
    }
    
    public string[] Categories
    {
        get { return m_cat; }
    }
}

public class Book : Item
{
    public Book( string desc, int quantity, decimal unitPrice, string[] 
    cat )
        : base( desc, quantity, unitPrice, cat )
    {
    }
}

public class PowerTool : Item
{
    public PowerTool( string desc, int quantity, decimal unitPrice, 
    string[] cat )
        : base( desc, quantity, unitPrice, cat )
    {
    }
}

Now that the basic basket items are working, let's actually build a collection of them to represent our shopping basket:

ArrayList basket = new ArrayList();

basket.Add( new Book( "Repair your car with twine", 100, 25,
                 new string[] { "Automotive", "Crafts" } ) );
basket.Add( new PowerTool( "Uber Drill 9000", 2, 1200,
                 new string[] { "Drills" } ) );
basket.Add( new PowerTool( "Texas Chainsaw", 1, 1700,
                 new string[] { "Saws", "Tree Care" } ) );
basket.Add( new Book( "Quantum Physics for Beginners", 2, 50,
                 new string[] { "Science" } ) );

Now we'll create an XPathNavigator instance to start using XPath with this object:

XPathNavigator    nav = new ObjectXPathNavigator( basket );
XPathNodeIterator iter;

Let's try it out. First I'll find all the PowerTool objects in the basket. The XPath query for this is "/*/PowerTool", meaning that I don't care what the document element node is called (that is, the name of the collection itself), but find all elements called PowerTool within it:

iter = nav.Select( "/*/PowerTool" );

while( iter.MoveNext() )
{
    // get the Description attribute (value of the Item.Description 
    property)
    Console.WriteLine(iter.Current.GetAttribute( "Description", 
    string.Empty ) );
}

When we run this code, we should see the following output:

Uber Drill 9000
Texas Chainsaw

What may not be obvious from this output is that I could actually do any query that XPath allows. Let's consider a much richer query, say finding all items in the basket with a line item total (unit price * quantity) over $2,000:

iter = nav.Select( "/*/*[(@UnitPrice * @Quantity) > 2000]" );

while( iter.MoveNext() )
{
    string itemTotal = iter.Current.Evaluate( "@UnitPrice * @Quantity" 
    ).ToString();

    // get the Description attribute (value of the Item.Description 
    property)
    Console.WriteLine(iter.Current.GetAttribute( "Description", 
    string.Empty )
                      + " = " + itemTotal );
}

When we run this code, we should see the following output:

Repair your car with twine = 2500
Uber Drill 9000 = 2400

Notice how the underlying XPathNavigator was able to query the UnitPrice and Quantity properties of the object, and multiply them together (even though one is a decimal type and the other an integer).

The XPath query here can be translated in layman's terms as, "I don't care what the document element node is called, and I don't care what the elements within it are called, but I want to find the ones where their UnitPrice * Quantity is over 2000."

Lastly, let's try some XSLT. We'll use a simple XSLT to group the Book and PowerTool objects separately and show them as XML. First, we need to load the XSLT stylesheet into memory:

XslTransform transform = new XslTransform();

// navigate from the bin\debug folder up to the project folder
string       path      = Path.Combine( Environment.CurrentDirectory,
                                       @"..\..\sample.xslt" );

// load the XSLT stylesheet
transform.Load( path );

The sample.xslt file is as follows (I've not included the whole file for the sake of simplicity):

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:msxsl="urn:schemas-microsoft-com:xslt">

    <xsl:template match="/">
        <!-- calculate the item totals -->
        <xsl:variable name="itemTotals">
            <xsl:for-each select="/*/*">
                <ItemTotal><xsl:value-of select="@UnitPrice * @Quantity" />
                </ItemTotal>
            </xsl:for-each>
        </xsl:variable>

        <basket>
            <!-- put the subtotal in an attribute -->
            <xsl:attribute name="SubTotal">
                <xsl:value-of select="sum(msxsl:node-set
                ($itemTotals)/ItemTotal)" />
            </xsl:attribute>
            
            <!-- copy the power tools -->
            <powertools>
                <xsl:copy-of select="/*/PowerTool" />
            </powertools>

            <!-- copy the Books -->
            <books>
                <xsl:copy-of select="/*/Book" />
            </books>
        </basket>
    </xsl:template>

</xsl:stylesheet>

One interesting feature of this XSLT is that it uses the sum() XPath function to calculate a subtotal of all of the items in the basket. To do this, I first got XSLT to create an in-memory XML document using <xsl:variable>, which I then populated with a set of nodes containing the calculated item total for a given line item:

<xsl:variable name="itemTotals">
    <xsl:for-each select="/*/*">
      <ItemTotal><xsl:value-of select="@UnitPrice * @Quantity" />
      </ItemTotal>
    </xsl:for-each>
</xsl:variable>

From there, calculating the total is achieved using sum() function of XPath as follows:

<xsl:value-of select="sum(msxsl:node-set($itemTotals)/ItemTotal)" />

Now let's create an XmlTextWriter so that we get nicely indented XML from the XSLT transform:

XmlTextWriter writer = new XmlTextWriter( Console.Out );
writer.Formatting = Formatting.Indented;

Finally, we need to create the ObjectXPathNavigator from our basket object and invoke the XSLT transform:

XPathNavigator nav = new ObjectXPathNavigator( basket );
transform.Transform( nav, null, writer );

When we run this code, we get this output:

<basket SubTotal="6700" xmlns:msxsl="urn:schemas-microsoft-com:xslt">
  <powertools>
    <PowerTool UnitPrice="1200" Quantity="2" Description="Uber Drill 
    9000">
      <Categories>
        <String>Drills</String>
      </Categories>
    </PowerTool>
    <PowerTool UnitPrice="1700" Quantity="1" Description="Texas Chainsaw">
      <Categories>
        <String>Saws</String>
        <String>Tree Care</String>
      </Categories>
    </PowerTool>
  </powertools>
  <books>
    <Book UnitPrice="25" Quantity="100" Description="Repairing your car 
    with twine">
      <Categories>
        <String>Automotive</String>
        <String>Crafts</String>
      </Categories>
    </Book>
    <Book UnitPrice="50" Quantity="2" Description="Quantum Physics for 
    Beginners">
      <Categories>
        <String>Science</String>
      </Categories>
    </Book>
  </books>
</basket>

IXPathNavigable—XPath Enabling Your Objects

Before going into the workings of the XPathNavigator, I wanted to point out one cool addition I could have made to this code, which can make the whole XPath thing easy to exploit in your existing applications.

System.Xml provides a little known interface called IXPathNavigable that implements a single method, CreateNavigator(), which returns an XPathNavigator instance. What's even more interesting is that the XSLT transform will accept an instance of this interface as a parameter in place of an XPathNavigator.

This leads us to an interesting idea. If you have objects you'd like to use with XPath or XSLT, why not make your objects implement IXPathNavigable and return a new ObjectXPathNavigator instance in the CreateNavigator() method, thus passing it into its constructor:

public class YourClass : IXPathNavigable
{
    public XPathNavigator CreateNavigator()
    {
        return new ObjectXPathNavigator( this );
    }

    // the rest of your class goes here
}

If I had built my own Basket class (instead of the simple ArrayList I used earlier) and implemented the IXPathNavigable interface, I could then pass the basket itself directly to an XSLT transform:

Basket basket = new Basket();

basket.Add( new Book( "Repair your car with twine", 100, 25,
                 new string[] { "Automotive", "Crafts" } ) );

XslTransform transform = new XslTransform();
transform.Load("sample.xslt" );

// now give the basket directly to the transform!
transform.Transform( basket, null, writer );

Understanding the XPathNavigator Cursor Model

As mentioned, XPathNavigator is built around a read-only cursor model. What that means is that instead of drilling down through a node structure the way you do with the Document Object Model, you instead have a cursor that you move around the navigator's underlying XML. Figure 2 shows some of the XPathNavigator cursor operations you might perform if you were currently located on an Item inside a Basket object.

Figure 2. Moving an XPathNavigator from one node to another in the XML

The code below shows the same structure in traditional XML form.

<Basket>
    <Item Desc="Uber Drill 9000">
        <Category />
    </Item>
    <Item />
</Basket>

The XPathNavigator Implementation

XPathNavigator is an abstract base class. That means by itself it doesn't implement a number of the properties and methods it exposes. If you wish to derive your own class from XPathNavigator, you must at least implement the abstract properties and methods listed in the tables below.

Abstract property Definition
BaseURI Gets the base URI for the current node.
HasAttributes Gets a value indicating whether the element node has any attributes.
HasChildren Gets a value indicating whether the current node has child nodes.
IsEmptyElement Gets a value indicating whether the current node is an empty element (for example, <MyElement/>).
LocalName Gets the name of the current node without the namespace prefix.
Name Gets the qualified name of the current node.
NamespaceURI Gets the namespace URI (as defined in the W3C Namespace Specification) of the current node.
NameTable Gets the XmlNameTable associated with this implementation.
NodeType Gets the type of the current node.
Prefix Gets the prefix associated with the current node.
Value Gets the text value of the current node.
XmlLang Gets the xml:lang scope for the current node.
Abstract method Definition
Clone() Creates a new XPathNavigator positioned at the same node as this XPathNavigator.
GetAttribute() Gets the value of the attribute with the specified LocalName and NamespaceURI.
GetNamespace() Returns the value of the namespace node corresponding to the specified local name.
IsDescendant() Determines whether the specified XPathNavigator is a descendant of the current XPathNavigator.
IsSamePosition() Determines whether the current XPathNavigator is at the same position as the specified XPathNavigator.
MoveTo() Moves to the same position as the specified XPathNavigator.
MoveToAttribute() Moves to the attribute with matching LocalName and NamespaceURI.
MoveToFirst() Moves to the first sibling of the current node.
MoveToFirstAttribute() Moves to the first attribute.
MoveToFirstChild() Moves to the first child of the current node.
MoveToFirstNamespace() Moves the XPathNavigator to the first namespace node of the current element.
MoveToId() Moves to the node that has an attribute of type ID whose value matches the specified string.
MoveToNamespace() Moves the XPathNavigator to the namespace node with the specified local name.
MoveToNext() Moves to the next sibling of the current node.
MoveToNextAttribute() Moves to the next attribute.
MoveToNextNamespace() Moves the XPathNavigator to the next namespace node.
MoveToParent() Moves to the parent of the current node.
MoveToPrevious() Moves to the previous sibling of the current node.
MoveToRoot() Moves to the root node to which the current node belongs.

The interesting thing about these methods is not the number, but that you don't have to provide an implementation for the most complex, yet most commonly used method, Select(). The Select() method is the XPath "magic happens here" function, which takes your XPath expression and tries to find nodes that match what was asked. The base XPathNavigator uses all those little atomic Move methods to walk the XML structure, querying attributes, and whatever else to find the requested nodes, independent of how the XML structure is physically implemented. This is one crucial advantage abstract base classes such as XPathNavigator have over interfaces. Whereas with an interface you have to provide all of the implementation, an abstract base class can at least do some common or complex tasks for you.

Building a Just-in-Time Object Proxy

So how does the cursor model map to an underlying node? Remember that in the Document Object Model you move from node to node, but with the cursor model you actually move the cursor. This means that in our model the cursor and the underlying node are not the same object.

To achieve this result, I built proxy objects that the navigator uses to drill up or drill down through the underlying object model. These proxy objects help when I need to move from node to node because I will have already worked out what nodes there are when I build the proxy. It also allows specialized handling for specific types such as collections and dictionaries without having to overcomplicate the navigator itself.

In order to get natural looking XML, I decided to map simple types like strings and numbers to attributes, and only go to a child element for a complex type such as a referenced object, or for collections and dictionaries.

Finally, as we may only touch a few of the child objects, I created the child object proxies as just-in-time so they only function when they're requested. That way I again save myself the cost of building proxies for the whole object graph (which at the end of the day would not be any better than serializing).

The role of the object proxy is to reduce the amount of effort it takes to navigate a given object by doing a one-time determination of the object's property values and references to other objects. So, how does the XPathNavigator cursor model play into all this? When you first create the ObjectXPathNavigator, an object proxy is created for the object you pass in (the one that will be navigated over). This object corresponds to the document element in XML. However, our initial cursor position is on what is known as the root node in XPath, which can be thought of as normally corresponding to the XML document itself rather than a specific node within it.

Figure 3. Relationship between the XPathNavigator and the Object Proxy

If you call MoveToFirstChild(), the cursor is moved to the object proxy of the document element , and the current node type is set to XPathNodeType.Element. From here we might navigate to the first attribute, or to this first child of the node. When we ask the object proxy for that information, it uses a just-in-time initialization mechanism to build out the attribute and child collections upon which we can then drill down. This pattern is then repeated as we move down through the tree.

The MoveToFirstChild() method also needs to have special handling for objects that expose text (say, a string object or other value type). In this case, we change the node type to be XPathNodeType.Text so that we know we are in the text context of the current object, not the element.

public override bool MoveToFirstChild()
{
    if( m_nodeType == XPathNodeType.Root )
    {
        // move to the document element
        this.MoveNavigator( m_docElem );                
        return true;
    }
    
    if( m_nodeType != XPathNodeType.Element )
    {
        return false;
    }

    // drop down to the text value (if any)
    if( m_currentElem.HasText )
    {
        m_nodeType   = XPathNodeType.Text;
        m_values     = null;
        m_valueIndex = -1;
        
        return true;
    }        

    // drop down to the first element (if any)
    IList coll    = m_currentElem.Elements;
                
    if( coll.Count > 0 )
    {
        MoveNavigator( (ObjectXPathProxy) coll[0] );
        
        return true;
    }

    return false;
}

The object proxy itself exposes several properties and methods for use by the XPathNavigator implementation, to abstract it from the underlying bound object. A typical ObjectXPathNavigator property is shown below.

public bool HasChildren
{
    get
    {
        Activate();

        return ( m_elements != null ) || HasText;
    }
}

Note the Activate() call, which ensures the proxy gets initialized correctly before I try to refer to the child elements of the node on the next line. The Activate() call performs a just-in-time initialization of our object the first time it is called. During this initialization proxy phase, I examine the properties of the bound object. If the property value returns a string or value type (such as an integer), I add it to a collection of attribute name/value pairs. Otherwise, I build another proxy bound to the object returned by the property, and add this newly created proxy to our Elements collection.

Why not just fully initialize the object in the constructor? Granted that's what it's for, but in this case we don't know when or even if the various methods in our object proxy will be called. The whole point of this exercise is to allow someone to arbitrarily query our object either directly through XPath or in a more complex fashion using XSLT. Secondly, there are some properties that do not need to call Activate() as they don't need or use the data that Activate() initializes. For example, the Value property, which simply returns the ToString() value of the object that the proxy is bound to:

public string Value
{
    get
    {
        if( HasText )
        {
            return m_binding.ToString();
        }
      
        return string.Empty;
    }
}

So, if I do nothing more than a <xsl:value-of select="." /> against our object proxy, I won't waste undue time initializing collections of child data that isn't required during this call.

Handling Attributes

One special consideration in the XPathNavigator implementation is the handling of attributes. For referenced objects, the object proxy builds an array of child object proxies, which then are exposed as child elements by the XPathNavigator.

String and values types, on the other hand, are implemented as a dictionary, mapping the original property name to its value, which the XPathNavigator needs to expose as attributes. While I could have made life easier by creating object proxies for each attribute, this would result in an undue memory and performance overhead (as the memory footprint for the proxy itself is much more than few bytes taken by most value types).

The solution I chose was to expose the attribute names as a collection. When the XPathNavigator is positioned on an attribute, I maintain an index to know which attribute name upon which XPathNavigator is resting If you request the Value of the XPathNavigator, I then use the indexed attribute name to look up its value from the proxy (performing a dictionary lookup to obtain the value). At this point, operations such as MoveToFirst() or MoveToNextAttribute() become as simple as modifying the attribute index.

Handling Collections and Dictionaries

So far we've considered the simple case of a property in the bound object having returned either a string/value type (which is treated as an attribute in our logical XML representation). Anything more complex has been treated as a child element, which works fine most of the time, but falls down for collections and dictionaries. The reason is that these types don't specifically expose the objects they contain in a simple 1:1 relationship the way most other properties do. To obtain a specific entry from the collection, you need to index it. So, we need special handling for collections and dictionaries.

The code snippet below shows the code the Activate() method calls when the proxy is bound to an object that implements ICollection (the ICollection interface is the standard base class implemented by all collection types in the .NET Framework).

private void ActivateCollection()
{
    // create a holding array
    ArrayList elements = new ArrayList();
    
    // go through each item in the array
    foreach( object val in (ICollection) m_binding )
    {
        // ignore null values
        if( val == null )
        {
            continue;
        }
        
        // build a proxy to the object, using its type as the
        // logical element name
        elements.Add( new ObjectXPathProxy( val, val.GetType().Name, this, 
        m_nt ) );
    }

    m_elements    = ( elements.Count != 0 ) ? elements : null;
} 

One interesting thing about this code is that it uses the types of the objects in the collection to name them in the XML. To understand how this works, let's imagine you have a shopping basket object called Basket, which contains a collection property called Items, which are the items in the basket. Here is what the XML output would look like if we had three objects in the Items collection (two of type Book and one of type PowerDrill):

<Basket>
    <Items>
        <Book />
        <Book />
        <PowerDrill />
    </Items>
<Basket> 

Dictionaries (objects implementing IDictionary) present an additional problem. Each entry in a dictionary has a key, but the key need not match a property associated with the value. Consider a dictionary that mapped U.S. state codes to the state names. In this case, we need to add a special attribute to the logical XML document to represent the key's value so that we can use this value to the query the XML representation. To register the special attribute, the object proxy exposes a method called AddSpecialName() that adds an entry to the attribute collection so that the XPathNavigator will see the attribute as if it was a property of the bound object.

The code below is called when you activate a dictionary object:

private void ActivateDictionary()
{
    ArrayList        elements = new ArrayList();
    ObjectXPathProxy item;

    foreach( DictionaryEntry entry in (IDictionary) m_binding )
    {
        if( entry.Value == null )
        {
            continue;
        }

        // create a proxy onto the entry's value
        item = new ObjectXPathProxy( entry.Value, 
        entry.Value.GetType().Name, this, m_nt );
        
        elements.Add( item );
        
        // register the key as a special attribute
        item.AddSpecialName( "key", entry.Key.ToString() );
    }

    m_elements    = ( elements.Count != 0 ) ? elements : null;
} 

In order to see how this looks in the real world, let's consider an example. Lets first create a simple dictionary:

IDictionary states = new Hashtable();

states["TX"] = "Texas";
states["WA"] = "Washington";
states["CA"] = "California"; 

Passing this to ObjectXPathNavigator would create the following logical XML document from this dictionary. Now we can use the special oxp:key attribute to query for the dictionary entry using its key.

<states xmlns:oxp="urn:ObjectXPathNavigator">
    <string oxp:key="TX">Texas</string>
    <string oxp:key="WA">Washington</string>
    <string oxp:key="CA">California</string>
</states>

Nested IXPathNavigable Not Supported

One specific case that is not supported in the current version of ObjectXPathNavigator is that of nested implementations of IXPathNavigable. If an object within your object graph implements IXPathNavigable, you might reasonably expect that it should provide XML in its own custom way.

As described earlier, objects implementing IXPathNavigable provide a CreateNavigator() implementation that can return an XPathNavigator. The issue in trying to support this in ObjectXPathNavigator is that the navigator the nested object returns would not know or expect to be part of a larger tree. If you were to call MoveToRoot(), it would move to the root of itself, not that of the ObjectXPathNavigator that contains it.

Of course you could work around this by building another proxy that wrapped the inner navigator and caught (and redirected as necessary) all of the calls. This gets beyond the scope of this article so I'll leave that implementation as an exercise to you, the gentle reader.

Conclusion

Using the ObjectXPathNavigator over an object graph provides the power and flexibility of the XML programming model without the overhead of having to serialize an entire object graph into an XmlDocument. Thanks to the compositional design of the System.Xml frameworks, this ObjectXPathNavigator can also be used in other XML processing scenarios. For example, in an XslTransform to achieve declarative XSLT transformation of any object graph.

Show:
© 2014 Microsoft