Wednesday, August 25, 2004

Namespace "inheritance" and XmlSerializer

I've taken to writing much of my XML like this:


<foo:bar xmlns:foo="uri">
    <blah ... />
    <blah ... />
</foo:bar>


Which is to say, with an outermost element that's namespace qualified, but with all other elements belonging to no namespace. This is primarily laziness - I type a lot of XML by hand, and write a lot of XPath, and both are slightly easier with this sort of XML.


Where I got burned today was in trying to write a set of types that deserialize this XML. I started with this:


[XmlRoot("foo", Namespace="uri")]
public class Foo {
  private BlahCollection blahs = new BlahCollection(); 
 
  [XmlElement("blah")]
  public BlahCollection Blahs {
    get { return blahs; }
  }
}


And I expected it would work just fine. But I was surprised to find that when I did the deserialization, I got zero blahs in the resulting collection, even though there were two in the file. After banging my head against this problem for quite some time, I finally realized I needed to do something like this:


[XmlRoot("foo", Namespace="uri")]
public class Foo {
  private BlahCollection blahs = new BlahCollection(); 
 
  [XmlElement("blah", Namespace="")]
  public BlahCollection Blahs {
    get { return blahs; }
  }
}


Note that I'm explicitly setting the namespace to the empty string. Apparently, what was happening was that without this explicit command, the [XmlElement("blah")] attribute was assuming the Namespace value of the [XmlRoot] attribute above it. So at deserialization time, it was looking for an element called "blah" from the "uri" namespace, but finding one called "blah" that was from no namespace.


Although I know I'm nothing like the first person to come across this, it isn't called out in the docs in a way that was obvious to me, so I thought I'd mention it here.

4 comments:

  1. Oops. Now that I've gotten some food :-) and read your post a little closer, I see that you meant for the blah elements to have no namespace, as indicated in your first paragraph and by your providing their parent element with a prefixed namespace. The assumption and suggestion in my first response was just wrong.



    You are quite correct that you have to explicitly clear the namespace on the Blahs member in the class in order for this to work.



    This becomes even clearer after deserializing an instance of your second class, which should produce something like this:



    <bar xmlns="uri">

    <blah xmlns="" ... />

    <blah xmlns="" ... />

    </bar>



    Which is the same as your original XML, but without the namespace prefix. Obviously, your version is easier to type by hand, which was your original point.



    Sorry for the confusion!

    ReplyDelete
  2. Don't sweat it: nothing I haven't done myself before. ;)

    ReplyDelete
  3. Just FYI, in XML Schema, there's a way of saying that you're using this style:



    <xsd:schema elementFormDefault=unqualified ...



    If you use this with XSD, it'll generate the classes like you are doing by hand.



    I totally agree that unqualified elements are much cleaner.

    ReplyDelete
  4. A good point. I should also note that I rarely write XSD schemas...I tend to just author classes using the XmlSerializer attributes directly. I rarely care about anything other than what the objects look like and what the XML looks like, and doing it this way means the things I have to remember are C# and straight XML things, rather than XSD things. Since I have to know C# and XML things anyway, this is less cognitive dissonance for me.



    Of course, I'm the sort of guy that tends to blow off wizards and write just about everything by hand.

    ReplyDelete