Custom Disassemblers
It's
important to call special attention to Disassembler components, as they
are often what most developers end up writing. Disassemblers were
intended to allow the pipeline to exam- ine the incoming document and
break it up into smaller, more manageable documents. The classic
example of this is an envelope file. The large document is received
that contains an envelope with multiple smaller documents inside it.
The envelope is removed, and each of the contained documents is
validated against its schema and ends up being a distinct and unique
message within BizTalk. This is shown in Figure 5. The Disassembler component has one key interface, IDisassemblerComponent.
IDisassemblerComponent has two methods, Disassemble and GetNext, which are listed in Table 3. What happens is the BizTalk runtime calls the Disassemble method first and passes the original message and the pipeline context. It then calls the GetNext method after the Disassemble method. The GetNext
method returns new messages of type IBaseMessage until the component
decides that all messages are created and then it returns Null.
Returning Null from GetNext signals the end of the component's execution and signals the runtime that all messages have been properly created.
Table 3. Public Methods of IDisassemblerComponent
Method | Description |
---|
Disassemble | Performs the disassembling of incoming document |
GetNext | Gets the next message from the message set resulting from the Disassembler execution |
A couple of design patterns exist that you can use when creating Disassemblers. One pattern is to use the Disassemble
method to prime any instance variables, setup, and data, and then
return and essentially create no messages. The messages will be created
in the GetNext method, and new messages will be created each
time the method is called. Another pattern is to create all messages in
the Disassemble stage, enqueue them to a queue structure, and then
dequeue the messages from the queue each time GetNext is
called. Either strategy will work; the second strategy can be more
efficient especially if expensive resources need to be instantiated
each time a message is created. Using the second method, you only need
to create these once at the beginning of the Disassemble
method, create all the messages, and then dispose of the resource.
Using the first method, the resource will either need to be created for
each GetNext() call or stored as an instance member of the
class. An example of the second implementation follows.
'<summary>
'called by the messaging engine until returned null, after Disassemble has been
'called
'</summary>
'<param name="pc">the pipeline context</param>
'<returns>an IBaseMessage instance representing the message created</returns>
Public Function GetNext(ByVal pc As _
Microsoft.BizTalk.Component.Interop.IPipelineContext) As _
Microsoft.BizTalk.Message.Interop.IBaseMessage Implements _
Microsoft.BizTalk.Component.Interop.IDisassemblerComponent.GetNext
'get the next message from the Queue and return it
Dim msg As Microsoft.BizTalk.Message.Interop.IBaseMessage = Nothing
If (_msgs.Count > 0) Then
msg = CType(_msgs.Dequeue, _
Microsoft.BizTalk.Message.Interop.IBaseMessage)
End If
Return msg
End Function
'<summary>
'called by the messaging engine when a new message arrives
'</summary>
'<param name="pc">the pipeline context</param>
'<param name="inmsg">the actual message</param>
Public Sub Disassemble(ByVal pc As _
Microsoft.BizTalk.Component.Interop.IPipelineContext, ByVal inmsg As _
Microsoft.BizTalk.Message.Interop.IBaseMessage) Implements _
Microsoft.BizTalk.Component.Interop.IDisassemblerComponent.Disassemble
'This is an example class which gets a simple list of strings. Each of
'these numbers will be
'a unique key in the new messages that we create.
Dim myArrayList As New ArrayList = myHelper.GetArrayofValues
Dim UniqueCode As String
For Each UniqueCode In myArrayList
_msgs.Enqueue(BuildMessage(pc, inmsg.Context, GetDocument _
(InboundSchema.DocumentSpec,UniqueCode)))
Next
End If
End Sub
Note
the following function. This is a general function that can be used in
any pipeline component where a new message needs to be created. This
function takes the pipeline context, the message context (which is
available from the original message), and the content for the document
as a string. A new message is returned with a cloned copy of the
original message context, and a message type as specified by the SchemaWithNone property.
'<summary>
'Returns a new message by cloning the pipeline context and original message context.
'The data to be assigned to the message must be a string value.
'</summary>
'<param name="pContextt">Pipeline context</param>
'<param name="messageContext">Original Message context to be used in the new
'message</param>
'<param name="messageContent">Data to be put in the message</param>
'<returns>New message</returns>
'<remarks>
'Message Content is assigned to the MessageBody by creating a new MemoryStream
'object
'</remarks>
Private Function BuildMessage(ByVal pContext As IPipelineContext, ByVal _
messageContext As IBaseMessageContext, ByVal messageContent As String) As _
IBaseMessage
' Build the message with its context
Dim message As IBaseMessage
Dim bodyPart As IBaseMessagePart
Dim messageStream As MemoryStream
Dim messageBytes As Byte()
Dim messageType As String
' Prepare and fill the data stream
messageBytes = Encoding.UTF8.GetBytes(messageContent)
messageStream = New MemoryStream(messageBytes.Length)
messageStream.Write(messageBytes, 0, messageBytes.Length)
messageStream.Position = 0
bodyPart = pContext.GetMessageFactory().CreateMessagePart()
bodyPart.Data = messageStream
message = pContext.GetMessageFactory().CreateMessage()
message.Context = PipelineUtil.CloneMessageContext(messageContext)
messageType = "http://" + _InboundDocumentSpecification.DocSpecName + _
"#" + _FileRootNode
message.Context.Promote("MessageType", BTSSystemPropertiesNamespace,_
messageType)
message.AddPart("body", bodyPart, True)
Return message
End Function