Friday, October 07, 2005

XmlSerializer bug when "xsi:type" is used in the XML

There’s a subtle bug in the .NET Framework v1.1 that I have just uncovered. Firstly, a little background into the issue, which is related to .NET XML serialization.

Consider following XML (later assumed to be in a file “text.xml”):

<?xml version="1.0" encoding="utf-8" ?> <Test>  <Persons xmlns=”http://tempuri.org/” xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”>   <Person xsi:type=”Male”>    <Name>Jeff</Name>    <FavouriteBeer>Monteith</FavouriteBeer>   </Person>   <Person xsi:type=”Female”>    <Name>Jessica</Name>    <FavouriteNailpolish>Revlon</FavouriteNailpolish>   </Person>  </Persons> </Test>

This would have been produced if you serialize the object of type Test, defined in the following code fragment:

[XmlIncludeAttribute(typeof(Male))] [XmlIncludeAttribute(typeof(Female))] [XmlTypeAttribute(Namespace=”http://tempuri.org/”)] public class Person {   public string Name; } [XmlTypeAttribute(Namespace=”http://tempuri.org/”)] public class Male : Person {   public string FavouriteBeer; } [XmlTypeAttribute(Namespace=”http://tempuri.org/”)] public class Female : Person {   public string FavouriteNailpolish; } [XmlTypeAttribute(Namespace=”http://tempuri.org/”)] [XmlRootAttribute(Namespace=”http://tempuri.org/”)] public class Test {   public Person[] Persons; }

The problem is that now you would not be able to deserialize the XML, if you write code similar to following:

XmlDocument doc = new XmlDocument(); doc.Load( “test.xml” ); XmlSerializer serializer = new XmlSerializer( typeof( Test ) ); Test test = (Test) serializer.Deserialize( new XmlNodeReader( doc ) );

And this is due to a bug in the XmlNodeReaderclass. I uncovered it only by stepping through the temporary serialization assembly generated by .NET framework. Without going into more detail, it is because LookupNamespace method does not return a canonical string from the reader’s NameTable. So the reference equality on the namespace inside the generated reader fails, and you get an exception similar to:

“The specified type was not recognized: name='Male', namespace='http://tempura.org/', at <Person xmlns='http://tempuri.org/'>.”

Fixing the problem is quite easy. All we have to do is write a class which extends from XmlNodeReader, and override the LookupNamespace method:

public class ProperXmlNodeReader : XmlNodeReader {   public ProperXmlNodeReader( XmlNode node ) : base( node )   {   }   public override string LookupNamespace(string prefix)   {     return NameTable.Add( base.LookupNamespace( prefix ) );   } }

Then you can use the ProperXmlNodeReader instead of the XmlNodeReader in the above example, and you should be able to deserialize the above XML with no problems.

No comments: