Search This Blog

2010-04-30

.NET + XPath + Namespaces

Solved in a subsequent post.

This post is somewhat of a continuation to my previous post. I managed to get LINQ to SQL entities with custom properties serialized fine, however, the output contains XML namespaces which leads me to my next brick wall.

The application that I'm working on uses XPath expressions to match data within the serialization XML. XPath is namespace aware, which means that if you have a structure like this:

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<root>
    <level1 xmlns="http://www.bamccaig.com/foo">
        <level2>Useful data!</level2>
    </level1>
</root>
 
 

Then the following XPath expression will NOT grab level2's text node:

/root/level1/level2/text()

The reason? level1 and level2 are in a different namespace than root. I have thus far failed to figure out how to work with this in .NET. In a past experience with this very problem not long ago, I was fortunate enough to be suggested a hack from the kind people on #xml on irc.freenode.net:

/*[local-name()='root']/*[local-name()='level1']/*[local-name()='level2']/text()

The asterisk (*) says to match all nodes (irrespective of namespaces) and the local-name() function returns the tag name (i.e., "root" for <root />). This works, or at least it did in the past, however, as you can imagine it's the Wrong Way(tm) and it's not particularly pretty either. The application I'm working on has a series of XPath expressions to maintain and I don't care to let this hack infect those.

Unfortunately, my attempts to solve this problem have so far failed. The XPathNavigator.Evaluate method accepts an IXmlNamespaceResolver, presumably for this very situation, but I'm unable to coerce it into resolving the namespace. :(

The most frustrating part of this is that the XML that it's [supposed to be] "navigating" is from the serialization process documented in my previous post. The XML was loaded with XmlDocument and the XmlNavigator was created with the XmlDocument's CreateNavigator method. The XmlDocument and XPathNavigator both have a NameTable property, which presumably maps out the namespaces within (it can be used to source an XmlNamespaceManager right in the constructor!), but somehow the XPathNavigator still fails to match to XPath expressions I'm feeding it. And get this: XPathNavigator implements the IXmlNamespaceResolver interface (and no, passing the navigator to the navigator does not work, I tried)!

I'm obviously missing something, but it's going to be nightmare to sort through all of the documentation, especially when I don't even know where to start...

Why, Microsoft?! Why?

Solved in a subsequent post.

LINQ to SQL + Serialization

So here I am for my first /real/ post. I recently faced off with serializing LINQ to SQL generated types with custom properties. "I" have done this before using WebMethods by simply returning them:

1
2
3
4
5
6
7
8
9
10
11
[WebMethod]
public EntityA GetEntityA(int id)
{
    using(var db = new MyDataContext())
    {
        return db.EntityAs.SingleOrDefault(
                o => o.id == id);
    }
}
 
 
Custom properties serialize fine that way and show up on the client-side (in JavaScript). The serialization is all handled for me by ASP.NET though. The way I originally was doing serialization is like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
var sb = new StringBuilder();
var xs = new XmlSerializerFactory(
        ).CreateSerializer(
        entityToBeSerialized.GetType());
 
using(var xw = XmlWriter.Create(sb))
{
    xs.Serialize(xw, entityToBeSerialized);
}
 
var serialized = sb.ToString();
 
 

This was working sufficiently for what I was using it for because the entities had no custom properties that were required in the serialization. Now I have entities that /do/ require their custom properties to be serialized. It took a little bit of Googling to find the solution (who has time to R all TFMs?). I found a solution that works (on MSDN, no less).

http://msdn.microsoft.com/en-us/library/bb546184.aspx

In short, you enable serialization for the LINQ to SQL model by opening it (the .dbml) in Visual Studio and setting the Serialization Mode (the only options I see are None and Unidirectional; guess which one to set it to). This tells LINQ to SQL to markup the generated types with serialization attributes. Next, I needed to markup my own custom properties with these same attributes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[DataMember]
public int SomeCustomProperty
{
    get
    {
        ...
    }
 
    set
    {
        ...
    }
}
 
 

Finally, I had to change the way I was serializing (my original way isn't aware of, or at least doesn't seem to care about, the attributes we've applied).

1
2
3
4
5
6
7
8
9
10
11
12
var dcs = new DataContractSerializer(
        entityToBeSerialized.GetType());
var sb = new StringBuilder();
 
using(var xw = XmlWriter.Create(sb))
{
    dcs.WriteObject(xw, entityToBeSerialized);
}
 
var serialized = sb.ToString();
 
 

This is what is currently working for me, at least. ;) Best practices and "'Ur Doin' It Wrong, n00b!" are welcomed. :P

** EDIT **

Note that the output is slightly different from the output of the original serialization classes, so code that you've written based on the old XML structure may need to be modified. I now have to remap a set of XPath expressions... Sigh. In truth, it's resulting in slightly better code though, IMHO.