XmlMessageFormatter Woes

posted @ Tuesday, August 15, 2006 6:00 PM

 

I ran into an issue with the XmlMessageFormatter yesterday where I noticed a difference between how .NET 1.1 and .NET 2.0 behave with streams and thought it would be good to share. We are pulling messages off a queue (ours happens to pull from Oracle AQ running on top of JMS). Since we don't know exactly what .NET type the XML message represents we can provide an XmlMessageFormatter with the types our application is aware of and ask it to perform the deserialization process for us. There is nothing complicated with this, internally it is just attempting to create an XmlSerializer for each registered type and asks if it can deserialize it for us. Let's look at a contrived example to see where the error occurs. Take the following class.

 

[Serializable()]
public class Person
{
private string name;
private int
age;

public string
Name
{
get { return name; }
set { name = value; }
}

public int Age
{
get { return age; }
set { age = value; }
}
}

In 2.0, we would write something like the following to fake our XML message that we want to deserialize:


string xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<Person xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " +
"xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">" +
"<Name>Nick Parker</Name>" +
"<Age>27</Age>" +
"</Person>";

Message msg = new Message();
UTF8Encoding encoding = new UTF8Encoding();
byte
[] data = encoding.GetBytes(xml);
MemoryStream stream = new MemoryStream(data);
msg.BodyStream = stream;

XmlMessageFormatter formatter = new XmlMessageFormatter();
formatter.TargetTypes = new Type[] {typeof (Person)};

if
(formatter.CanRead(msg))
{
Person p
= formatter.Read(msg) as Person;
Console.WriteLine("Name:{0}\nAge:{1}", p.Name, p.Age);
}

This is fine and dandy until we try to execute the same code in .NET 1.1, we will get an XmlException when we attempt to call Read, so what's the problem? As it turns out, when we call CanRead internally the XmlMessageFormatter creates an XmlTextReader passing the BodyStream of the message into the constructor. As it reads the contents of the XML, the Position property is never reset - CanRead leaves the stream position in an invalid state, thus when Read is called the stream is at the end and throws our exception. The following is an example that will work in .NET 1.1, as you can see we are simply resetting the Position after the call to CanRead.

 

string xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<Person xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " +
"xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">" +
"<Name>Nick Parker</Name>" +
"<Age>27</Age>" +
"</Person>";

Message msg = new Message();
UTF8Encoding encoding = new UTF8Encoding();
byte
[] data = encoding.GetBytes(xml);
MemoryStream stream = new MemoryStream(data);
msg.BodyStream = stream;

XmlMessageFormatter formatter = new XmlMessageFormatter();
formatter.TargetTypes = new Type[] {typeof (Person)};

if
(formatter.CanRead(msg))
{
msg.BodyStream.Position
= 0;
Person p = formatter.Read(msg) as Person;
Console.WriteLine("Name:{0}\nAge:{1}", p.Name, p.Age);
}

Microsoft has added a knowledge base article confirming this type of error here.