I’ve been working a lot with low-level contracts in Windows Communication Foundation that send and receive raw System.ServiceModel.Channels.Message objects. These are fairly flexible, though the API can be a bit confusing.
One of the things I was doing was making sure I was doing proper streaming of large messages, which in WCF relies on the XmlReader and XmlWriter classes.
What is not be very obvious at first is that it is possible to receive a Message object and still do streaming: Basically when the received message is created, it takes an XmlReader that is only consumed when you ask WCF to write the message to an XmlWriter.
Internally, the message will read each node from the XmlReader and write it to the XmlWriter, which works very well for the streaming. But it can also trip you sometimes, like what happened to me recently:
Basically, my WCF service was receiving an streamed XML file, went up to the Message-based contract, and then I streamed the message out to a file. With some message payloads, I’d sometimes get the following error:
System.InvalidOperationException: Token Text in state EndRootElement would result in an in valid XML document. Make sure that the ConformanceLevel setting is set to ConformanceLevel.Fragment or ConformanceLevel.Auto if you want to write an XML fragment.
I opened up the original message and as far as I could see it was perfectly well-formed XML. Until I opened the file in a hex editor and realized it had a Line Feed (\n) character right at the end after the closing tag!
You can, of course, fix the error by making sure the source doesn’t have any characters after the closing tag. However the exception also suggest another workaround which can be more useful when you don’t control the input, like in a WCF service: Set the XmlWriter to the Fragment conformance level, which won’t give you trouble with the extra \n at the end.
Here's a simple console application that exhibits the problem:
using System.IO;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Xml;
class Program {
public static void Main() {
string xml = "
Start
End \n";
MessageEncoder encoder = CreateEncoder();
Message msg = encoder.ReadMessage(ToStream(xml), Int32.MaxValue);
try {
XmlWriterSettings set = new XmlWriterSettings();
// Uncomment this line to avoid the exception:
//set.ConformanceLevel = ConformanceLevel.Fragment;
XmlWriter xmlWriter = XmlWriter.Create(Console.Out, set);
XmlDictionaryWriter xmlDictWriter =
XmlDictionaryWriter.CreateDictionaryWriter(xmlWriter);
msg.WriteBodyContents(xmlDictWriter);
xmlDictWriter.Flush();
} catch ( Exception ex ) {
Console.WriteLine("\n****************");
Console.WriteLine(ex.ToString());
Console.WriteLine("****************");
}
}
static Stream ToStream(String text) {
MemoryStream stream = new MemoryStream();
StreamWriter writer = new StreamWriter(stream);
writer.Write(text);
writer.Flush();
stream.Position = 0;
return stream;
}
static MessageEncoder CreateEncoder() {
TextMessageEncodingBindingElement tmebe =
new TextMessageEncodingBindingElement();
tmebe.MessageVersion = MessageVersion.None;
MessageEncoderFactory factory = tmebe.CreateMessageEncoderFactory();
return factory.Encoder;
}
}