Making the original data stream seekable in a custom BizTalk pipeline
This blog post is about creating a custom BizTalk pipeline that allows the original data stream to be seekable. If the position of this stream is not reset when it’s returned then the message will be empty as it is by default forward-only.
A seekable stream in a pipeline
I won’t go into details of how the pipeline is structured, which interfaces to implement, etc. You can find the full documentation on how to create a custom BizTalk pipeline at this MSDN article.
Let’s first have a look at the most important method of the pipeline: Execute. This is the main method of the pipeline which is called with context and the actual message being processed is passed to it as parameters.
1 |
<span id="lnum1" style="color: #606060"> 1:</span> <span style="color: #0000ff">public</span> IBaseMessage Execute(IPipelineContext pContext, IBaseMessage pInMsg) |
1 |
<span id="lnum2" style="color: #606060"> 2:</span> { |
1 |
<span id="lnum3" style="color: #606060"> 3:</span> <span style="color: #008000">// Create a seekable message from the IBaseMessage passed to the pipeline</span> |
1 |
<span id="lnum4" style="color: #606060"> 4:</span> IBaseMessage seekableMessage = CreateSeekableMessage(pInMsg); |
1 |
<span id="lnum5" style="color: #606060"> 5:</span> |
1 |
<span id="lnum6" style="color: #606060"> 6:</span> <span style="color: #0000ff">try</span> |
1 |
<span id="lnum7" style="color: #606060"> 7:</span> { |
1 |
<span id="lnum8" style="color: #606060"> 8:</span> DoSomethingWithMessage(seekableMessage); |
1 |
<span id="lnum9" style="color: #606060"> 9:</span> } |
1 |
<span id="lnum10" style="color: #606060"> 10:</span> <span style="color: #0000ff">catch</span> (Exception ex) |
1 |
<span id="lnum11" style="color: #606060"> 11:</span> { |
1 |
<span id="lnum12" style="color: #606060"> 12:</span> <span style="color: #008000">// If we cannot do something with the message then write to the event log that the pipeline failed</span> |
1 |
<span id="lnum13" style="color: #606060"> 13:</span> EventLog.WriteEntry(<span style="color: #006080">"DBLOG.BizTalk.PipelineSample"</span>, ex.Message, EventLogEntryType.Error); |
1 |
<span id="lnum14" style="color: #606060"> 14:</span> } |
1 |
<span id="lnum15" style="color: #606060"> 15:</span> <span style="color: #0000ff">finally</span> |
1 |
<span id="lnum16" style="color: #606060"> 16:</span> { |
1 |
<span id="lnum17" style="color: #606060"> 17:</span> <span style="color: #008000">// Always reset the message stream</span> |
1 |
<span id="lnum18" style="color: #606060"> 18:</span> ResetMessageStream(seekableMessage); |
1 |
<span id="lnum19" style="color: #606060"> 19:</span> } |
1 |
<span id="lnum20" style="color: #606060"> 20:</span>&nbsp; |
1 |
<span id="lnum21" style="color: #606060"> 21:</span> <span style="color: #008000">// Pass the message back regardless if we managed to do something with it or not</span> |
1 |
<span id="lnum22" style="color: #606060"> 22:</span> <span style="color: #0000ff">return</span> seekableMessage; |
1 |
<span id="lnum23" style="color: #606060"> 23:</span> } |
From the code sample above we have two interesting things happening:
- A method called CreateSeekableMessage is called which takes the type IBaseMessage as parameter
- A method called ResetMessageStream is finally called before the message is returned
Let’s have a closer look at the CreateSeekableMessage-method and see exactly what it does.
1 |
<span id="lnum1" style="color: #606060"> 1:</span> <span style="color: #0000ff">private</span> IBaseMessage CreateSeekableMessage(IBaseMessage message) |
1 |
<span id="lnum2" style="color: #606060"> 2:</span> { |
1 |
<span id="lnum3" style="color: #606060"> 3:</span> Stream originalDataStream = message.BodyPart.GetOriginalDataStream(); |
1 |
<span id="lnum4" style="color: #606060"> 4:</span>&nbsp; |
1 |
<span id="lnum5" style="color: #606060"> 5:</span> <span style="color: #008000">// Only create the new seekable stream if the original data stream does not have seek capability</span> |
1 |
<span id="lnum6" style="color: #606060"> 6:</span> <span style="color: #0000ff">if</span> (!originalDataStream.CanSeek) |
1 |
<span id="lnum7" style="color: #606060"> 7:</span> { |
1 |
<span id="lnum8" style="color: #606060"> 8:</span> ReadOnlySeekableStream seekableDataStream = <span style="color: #0000ff">new</span> ReadOnlySeekableStream(originalDataStream); |
1 |
<span id="lnum9" style="color: #606060"> 9:</span> message.BodyPart.Data = seekableDataStream; |
1 |
<span id="lnum10" style="color: #606060"> 10:</span> } |
1 |
<span id="lnum11" style="color: #606060"> 11:</span>&nbsp; |
1 |
<span id="lnum12" style="color: #606060"> 12:</span> <span style="color: #0000ff">return</span> message; |
1 |
<span id="lnum13" style="color: #606060"> 13:</span> } |
In this method a new object of type ReadOnlySeekableStream is created from the original data stream. From here on the stream will be seekable which means we can read from it and ultimately reset the position and pass it back and into BizTalk’s message box!
So without pause let’s dive into the ResetMessageStream-method!
1 |
<span id="lnum1" style="color: #606060"> 1:</span> <span style="color: #0000ff">private</span> <span style="color: #0000ff">void</span> ResetMessageStream(IBaseMessage message) |
1 |
<span id="lnum2" style="color: #606060"> 2:</span> { |
1 |
<span id="lnum3" style="color: #606060"> 3:</span> <span style="color: #008000">// Reset the stream to position 0</span> |
1 |
<span id="lnum4" style="color: #606060"> 4:</span> message.BodyPart.Data.Seek(0, SeekOrigin.Begin); |
1 |
<span id="lnum5" style="color: #606060"> 5:</span> } |
This method does just what it says: resets the stream of the message-object to position 0!
Final words
When creating pipelines try to keep the complexity to a minimum. I have seen pipelines where all logic has been stuffed into the Execute-method. While the pipeline might do its job the next developer having a look at your code is going to have a bad day.
Try to make your code as readable as possible!