I recently wrote a pipeline that needed to access the data, in order to use that data for setting one of the context properties (I can explain why in a future blog). In the past, I might have used XmlDoc.Load and used XPath to process the document, but these days, I’m more savvy on how to use XmlTextReader.
I want to summarize and add some comments about an issue I recently had (reported on MSDN forum, but solved myself):
By using a “Stream,” that data can be processed in small chunks. With the BizTalk “VirtualStream” class/object, you can even set the buffer size. For more info, check out this blog for great background info on .NET Streams.
So here’s my tale of woe. I created a pipeline, processed the stream using XmlTestReader, found the desired data and used it to set the OutboundTransportLocation (the URL of a WCF service, that I was trying to make dynamic, while yet using a static port). My code worked fine when I hardcoded a value, but once I put in the XmlTextReader, it started blowing up with this error:
System.Xml.XmlException: Root element is missing. at System.Xml.XmlTextReaderImpl.Throw(Exception e) at System.Xml.XmlTextReaderImpl.ParseDocumentContent() at System.Xml.XmlTextReaderImpl.Read() at System.Xml.XmlReader.MoveToContent() at Microsoft.BizTalk.Adapter.Wcf.Runtime.BizTalkBodyWriter.ValidateStreamContainsXml(Stream stream) at Microsoft.BizTalk.Adapter.Wcf.Runtime.WcfMarshaller.CreateWcfMessage(CreateWcfMessageSettings settings) at Microsoft.BizTalk.Adapter.Wcf.Runtime.WcfClient`2.SendRequestMessage(IBaseMessage bizTalkMessage, IRequestChannel channel) at Microsoft.BizTalk.Adapter.Wcf.Runtime.WcfClient`2.SendMessage(IBaseMessage bizTalkMessage)
I searched the web for solutions, and didn’t find anything obvious, which is why I’m creating this blog. Somewhere from back in the deep dark memories of my brain, I recalled something about doing a Seek in pipelines, so I “googled” and found this article titled: Handling Incoming Data Streams in Pipeline Components.
That article made perfect sense. It says:
If you do not do this and the stream is read to the end in the current component, the next component receives what appears to be an empty stream because the data stream pointer was not set to the start of the stream. This can cause unexpected parsing and validation errors in subsequent pipeline components.
So if I’m at the end of the stream, and the stream gets passed on to the next step, and that step tried to read the stream, it would start at the current position of the stream, which could be the middle or end of the stream (depending on how I coded my XmlReader loop). Actually, I stopped as soon as I found the value I needed, so most likely, I left the seek position in the middle of the stream. So the next XmlTextReader starts from there, and it finds no root element, or basically invalid XML. If you take the second half of an XML file, it would not be well-formed; it would be missing it’s root element, and the final element wouldn’t match back to the root.
I cannot include my entire pipeline here, but here is short sample code using Virtual Stream in a BizTalk Pipeline. Instead of using the “Seek” method, that codes does the following to set the “Position” property, which accomplishes the same:
vStream.Position = 0;
For further info: MSDN Article: Optimizing Pipeline Performance
Filed under: Pipelines