4. Custom Disassemblers
It's important to call special
attention to Disassembler components, because they are often what most
developers end up writing. Disassemblers were intended to allow the
pipeline to examine 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, as shown in Figure 4. 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 method, 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 need to create
these only 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. For this
example, assume that this code is cumulative with the previous examples.
In this case, a variable named _InboundDocumentSpecification is used. This is the SchemaWithNone variable we explained in the previous section that allows us to see the developer-requested "new document schema type."
'<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 Function GetNext(ByVal pc As _
Microsoft.BizTalk.Component.Interop.IPipelineContext) As _
Microsoft.BizTalk.Message.Interop.IBaseMessage Implements _
Microsoft.BizTalk.Component.Interop.IDisassemblerComponent
'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 ArrayList = myHelper.GetArrayofValues
Dim UniqueCode As String
'Essentially it is a
'function that returns an empty XML Document as a
'string given a fully qualified and
'deployed BizTalk schema
For Each UniqueCode In myArrayList
_msgs.Enqueue(BuildMessage(pc, inmsg.Context, GetDocument _
(InboundSchema.DocumentSpec, UniqueCode)))
Next
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="pContext">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