August 2006 Entries

XmlMessageFormatter Woes

 

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.

A Good Feeling

 

Our team adopted a continuous integration server a little over a month ago and we have been very happy with the results since. We were using Visual Source Safe as our source repository and CC.NET for a continuous integration server. A special "builder" account was created in VSS for CC.NET to pull down the source for each project it had registered. Our projects are set to trigger within CC.NET every 15 minutes. Over time, as we added more projects, CC.NET would report a failed build sporadically. The build error alluded to a VSS user specific log file that was inaccessible during the period of time CC.NET would try to access the repository. This file was locked during a parallel/previous build by VSS - this is a known issue and has been discussed on the CC.NET forums. All-the-while, we continued to have various issues with VSS that would cause us to reboot the repository server from time to time - totally unacceptable in my eyes. It was time for a better source control system to step up to bat, welcome Subversion to the team. I have been using Subversion for a while for personal projects, but as of Friday last week we have migrated our repository from VSS and our entire team is now using Subversion. Our CC.NET is running much smoother now, in fact we haven't had any issues as of yet. We are using AnkhSVN for Visual Studio integration and TortoiseSVN for integration within the Windows shell. I can gladly say goodbye to pessimistic locking for good now.