This article has been superseded by a new article.
In the last few weeks, we've encountered a set of problems when using custom pipeline components in Receive pipelines. After some testing, I have managed to produce a fairly definitive description of the behaviour we have encountered. I have no very compelling explanation for what we have found, but the problems are entirely repeatable, and it is obvious from looking at BizTalk newsgroups that we are not the only people who have suffered from these problems.
In essence, if your pipeline contains the Xml Disassembler component, you are significantly restricted as to what you can do in any pipeline component you place in a subsequent stage (the Validate and ResolveParty stages), especially if you decide to assign an inbound map to your Receive port. If you don't have a disassembler component in your pipeline, then everything works as you might expect from the documentation and examples provided by Microsoft. So far, I haven't completed any testing using custom disassemblers.
Let's take a simple scenario. You create a custom pipeline that will process inbound XML messages. The custom pipeline contains the Xml Disassembler in order to detect the type of XML message. This is one of the most basic uses of the XML Disassembler, which appends a MessageType promoted property to the message (as well as a schema reference). When this property is set, BizTalk enables any inbound map set on the port. You then create a pipeline component that will process the XML data within the message, making some fine adjustments (perhaps replacing an existing text node with a new value). In real life, you might well want to do this is a situation where you are using an envelope to disassemble a single XML message into multiple messages. You therefore want to place your pipeline component downstream of the Xml Disassember. Armed with a knowledge of pipeline components based on Microsoft's current documentation and examples, you confidently place your pipeline component in the Validate stage and deploy your BizTalk application.
Your pipeline probably won't work. Let’s say that your custom pipeline component attempts to process the XML data stream directly. Perhaps it attempts to write directly to the data stream provided by the Xml Disassembler. This certainly won't work. The Xml Disassembler wraps its data stream in an instance of a class called XmlDasmStreamWrapper. The underlying stream is an instance of a class called ForwardOnlyEventingReadStream. The name of this class indicates fairly clearly why things won't work. The stream is non-seekable (forward-only) and read-only. You can't write to the stream and, having read the stream, you cannot reposition the stream pointer to the beginning of the stream again (e.g., by using myStream.Seek(0, SeekOrigin.Begin);). If you read the stream, the map will fail, presumably because the stream is no longer positioned at the beginning of the data.
In passing, note that the general convention for pipeline components is that while the underlying stream is provided by calling GetOriginalDataStream() on a message part, you can obtain a second cloned stream by using the Data property. Unfortunately, the Xml Disassembler does not comply with this convention. The same stream is returned by both approaches. I hunted around in the hope that the stream class might be storing the steam data in a private byte array that I could read through reflection. However, unfortunately, the only thing I found was a buffer (the XmlDasmStreamWrapper class builds a buffer around the base stream), which of course is only filled if you read the stream. In short, there doesn't appear to be any way getting at the data, let alone changing it, without adjusting the position to a non-zero value, and if you do this, your inbound map will break.
Now, let’s say that you change your custom pipeline component. You read the entire stream into a string, process the string and then write the string out to a new, seekable, stream. You reposition at the beginning of the stream and assign this stream back to the body part of the message. This is one scenario that might possibly work. As long as you don't have an inbound map, the amended message will make it through to the message box. However, if you do have an inbound map on your Receive port, this will fail with a 'The root element is missing" error. This error typically indicates that a stream containing XML has been read, and is not positioned at the beginning of the stream. Hence, my best guess is that the inbound map is attempting to process the stream provided by the Xml Disassembler, and is not using the new stream. The original stream has been read, and is positioned at the end of the stream. Because it is non-seekable, there is no way you can reposition back to the beginning of the stream. [Update - 29th Sept: My 'best guess' turned out to be incorrect. The map is using the stream provided on the body part of the message returned by the last pipeline component, as you might expect. The issue is that the map will transform data sucessfully if this stream is non-seekable. If it is seekable, BizTalk fails as described. This appears to be what my 12-year old daughter might call a 'random' er.. feature (as in "oh, Dad, you're so random"].
Now, to see how truly bizarre things get, let’s imagine a slightly different scenario. Let’s say that your pipeline component, for some reason, does not process the stream provided by the Xml Disassembler, but instead creates a new stream from scratch. The important point here is that the original stream remains unread. Based on the previous paragraph, you might expect that an inbound map would now work. This is not the case, however. Instead, regardless of whether you have an inbound map or not on your Receive port, your pipeline will fail. In fact, BizTalk will pretty much lock up. The BizTalk engine enters a never-ending loop in which it repeatedly re-executes the Validate and ResolveParty stages of your custom pipeline. The only way out of this is to kill the BTSNTSvc.exe service and then quickly un-deploy your BizTalk assembly containing the custom pipeline. If you don't un-deploy, the BizTalk service will be automatically re-started after a few seconds and will re-enter the closed loop. [Update - 28th Sept: I believe I can now explain this behaviour, and have added a feedback comment discussing why I believe this happens. It appears to be a 'feature' of the XMLDisassembler component and the XmlDASMReader class used by XmlDasmStreamWrapper].
You can easily suppress this behaviour. All you have to do is make sure that you have read the original stream provided by the Xml Disassembler. Of course, if you do this, your inbound map won't work (you'll get the "The root element is missing" error). However, if you don't have an inbound map, a message will be placed in the message box containing the contents of your new stream.
So there you have it. If any of this is by design, then it rather suggests that Microsoft's intention is that, having used the Xml Disassembler, you should treat the body part data stream as immutable. [I'm less and less convinced any of this is 'by design'] This makes some sense, as the Xml Disassembler has specified the XML type within the message context, and you might inadvertently invalidate this information by any changes you make. However, if this is intentional, it should be documented. There is no hint anywhere that I can find in the existing documentation that this a design feature. The closed-loop behaviour feels like a bug. [again, see the feedback comment below, which discusses this issue further] I must admit that, generally speaking, you would not want to replace an unread stream provided by a disassembler component with a new stream. However, neither should we have a situation where the BizTalk pipeline disappears forever into a black hole.
If you want to adjust the contents of a message after the disassemble stage, and also use an inbound map, you must either use functoids within your map to make the adjustments, or make the changes in a pipeline component and move your map into an orchestration.
You might be wondering about Microsoft's XML Validation and Party Resolution components. I spent some time looking at these, and can confirm that neither of these components actually reads the body part data stream. The validator, for example, simply wraps the unread stream in an XMLValidatingStream instance. The validation is therefore done when the streeam is next read (probably by the the inbound map). The Party Resolution component doesn't read the stream either. It only changes the message context.