UnzipDisassembler - A custom pipeline component for BizTalk Server 2004






4.87/5 (17 votes)
Jan 29, 2006
9 min read

243762

887
This article shows how to create a custom pipeline component in BizTalk Server 2004. The component receives a zipped message, uncompresses it, and returns the .zip file contents to the BizTalk Server.
Introduction
In this article, we'll see how to create and use a custom pipeline component in BizTalk Server 2004. Our component will receive a ZIP file, uncompress it, and send its contents to BizTalk Server. Although the article seems complicated, you'll see that create a custom pipeline component is quite a simple task. Our project will use a free Zip library, the ICSharpZipLib. This library can be found at the IC#Code web site.
Creating a Custom Pipeline
A Custom Pipeline component is nothing more than a simple .NET DLL that implements a class with some predefined interfaces. This interface represents the layer between a .NET Program and then BizTalk Server. We'll start creating a new C# Class Library project called UnzipDisassembler.
Now that we created the project, we need to add our references. The first reference we'll need is that to the ZIP Library. After downloading the latest version of it, add a reference to our project.
After adding the reference to the Zip Library, we'll need a reference to a BizTalk Server library. This library can be found at the BizTalk Server install folder (\Program Files\Biztalk Server 2004, to be more precise). The name of this library is Microsoft.Biztalk.Pipeline.dll, it contains the basic interfaces we'll need to process the Zip file message.
Coding the class
Now, we'll start coding our custom pipeline component. We'll start creating the using
clauses necessary for the implementation of the pipeline. The list of the using
statements is:
using System;
using System.IO;
using Microsoft.BizTalk.Component.Interop;
using Microsoft.BizTalk.Message.Interop;
using System.ComponentModel;
using System.Resources;
using ICSharpCode.SharpZipLib.Zip;
Now, we'll update the name of the class in the project. Change the default Class1
name to UnzipDisassemblerComponent
(you can change the name of the physical file as well). The class should have a similar structure as shown below:
namespace UnzipDisassembler
{
/// <SUMMARY>
/// Summary description for Class1.
/// </SUMMARY>
public class UnzipDisassemblerComponent
{
public UnzipDisassemblerComponent()
{
//
// TODO: Add constructor logic here
//
}
}
}
Implementing the Interfaces
To create a custom pipeline component, we need to implement some default interfaces. If we want to create a generic pipeline component, we need to implement IBaseComponent
, IComponent
, IComponentUI
, and IPersistPropBag
interfaces. Since we're working with the disassembler component, we'll not use the IComponent
interface, we'll replace it for IDisassemblerComponent
. Besides using these interfaces, we need to add some attributes to our class. These attributes are the "Component Category Attributes", that indicate that our class is a custom pipeline component, and the System.Runtime.InteropServices.Guid
, that we'll use to generate a unique identifier for our custom pipeline component. You can obtain a new GUID to your component using Visual Studio: in the Tools menu, use the Create GUID option. The implementation of the class should be similar to the code shown below:
[ComponentCategory(CategoryTypes.CATID_PipelineComponent)]
[ComponentCategory(CategoryTypes.CATID_DisassemblingParser)]
[System.Runtime.InteropServices.Guid("
6118B8F0-8684-4ba2-87B4-8336D70BD4F7")]
public class UnzipDisassemblerComponent : IBaseComponent,
IDisassemblerComponent,
IComponentUI,
IPersistPropertyBag
{
public UnzipDisassemblerComponent()
{
//
// TODO: Add constructor logic here
//
}
}
Now that we've added the interfaces to the class, we need to implement them. We'll start with the IBaseComponent
interface.
IBaseComponent
The fist interface we'll implement is the IBaseComponent
. This interface has three properties that we must implement. To implement these properties, we'll use the Class Viewer. If the class viewer is not visible, go to the View menu and select Class Viewer. In the Class Viewer, expand our newly created class (UnzipDisassemblerComponent
), expand Bases and Interfaces, and you'll see the four interfaces we have implemented. Expand the IBaseComponent
, and you'll see the Class Viewer as shown below:
Right click the Description
property and select the Add>Override option. A new function will be created in your class. Repeat the process for the other two properties.
Each one of this properties returns some information about your custom pipeline component. The Name
property returns the name of the component. The Description
property returns a brief description of what you component does. The Version
property returns the current version of the component. The code below has the implementation of these three properties:
public string Description
{
get {
return "Componente de descompactação para Biztalk";
}
}
public string Name
{
get{
return "UnzipDisassemblerComponent";
}
}
public string Version
{
get{
return "1.0.0.0";
}
}
IComponentUI
This interface defines the behavior of the component in the BizTalk pipeline designed. We'll need to implement a method and a property. The Validate
method is used to validate the properties that are set in the custom pipeline component, and it's called when a pipeline that uses the pipeline component is compiled. Since we're not implementing any properties here, we'll use the default behavior of this method. The Icon
property represents the icon associated with the component (shown in the toolbox). In this case, we'll use the default icon, so let's leave the code as is. The complete implementation of this interface is shown below:
public System.Collections.IEnumerator Validate(object projectSystem)
{
return null;
}
public System.IntPtr Icon
{
get
{
return new System.IntPtr ();
}
}
IPersistPropertyBag
The IPersistPropertyBag
interface is used to store the properties that are set in the pipeline component in the pipeline designed. We use the InitNew
, Load
, and Save
methods. In our component, we're not using properties, so we don't need any implementation here. We just have to implement this interfaces because they are mandatory.
public void GetClassID(out Guid classID)
{
classID = classID = new Guid("6118B8F0" +
"-8684-4ba2-87B4-8336D70BD4F7");
}
public void InitNew()
{
}
public void Load(IPropertyBag propertyBag, int errorLog)
{
}
public void Save(IPropertyBag propertyBag,
bool clearDirty, bool saveAllProperties)
{
}
Note that the GetClassID
property should return the same GUID used in the class attributes of the component.
IDisassemblerComponent
This interface is the most important of our component. The Disassemble
method is responsible for the process of mounting the messages to BizTalk. With this component, we can receive a message as an input and generate many messages as output. As an example, we can have a single .ZIP file with multiple XML files in it. We need to consider each XML file as a single message. To do this, our disassemble component must create a list of output messages. The GetNextMessage
is called just after the disassemble method. This method is responsible for returning the messages to BizTalk, and it executes as long as the method returns a message. When the method returns null
, it considers that there are no more messages to be retrieved from the original assembled message. Implement the two methods of this interface using the class wizard. The code should be like the one shown below:
public void Disassemble(IPipelineContext pContext, IBaseMessage pInMsg)
{
}
public IBaseMessage GetNext(IPipelineContext pContext)
{
return null;
}
Implementing the Unzip Logic
The unzip logic will be implemented inside the Disassemble
method. This method receives as an input parameter a context and a message. The context parameter represents the execution context of BizTalk Server, and the message parameter represents the zipped message itself. The message is represented by an interface called IBaseMessage
. This interface has information about the message (context) as well as the body of the message, that can be obtained from the Body
property. The body of the message (as well as any other "part" of the message) is represented by the IBaseMessagePart
interface. This interface has some methods that allows us to read the message as an input stream. The code below obtains the body of the received message as a stream (this code should be implemented in the Disassemble
method):
Stream strmZipFile;
IBaseMessagePart msgPart;
msgPart = pInMsg.BodyPart;
strmZipFile = msgPart.GetOriginalDataStream();
ZipInputStream oZipStream = new ZipInputStream(strmZipFile);
Note that we're creating a ZipInputStream
from the original stream. This kind of a stream is available in the Zip library, and we'll use it to uncompress the message. Since our file can have more than one uncompressed files in it, we'll store the uncompressed files generated in a Queue
object. To do this, we'll implement a new instance of the Queue
class in our class.
System.Collections.Queue qOutputMsgs = new System.Collections.Queue();
Now, let's go back to the Disassemble
method. The ZipInputStream
class has a method called GetNextEntry
that returns an entry to an uncompressed file inside the Zip file. Each entry represents a single file in the original Zip file. Using the code below, we'll extract this entry from the Zip file and create a new output message to BizTalk, adding it to our Queue
.
ZipEntry sEntry = oZipStream.GetNextEntry();
while(sEntry != null)
{
MemoryStream strmMem = new MemoryStream();
byte[] aBytes = new byte[2047];
int inOffSet = 0;
int outOffSet = 0;
int iRead = oZipStream.Read(aBytes, 0, 2048);
while(iRead != 0)
{
strmMem.Write(aBytes, outOffSet, iRead);
outOffSet += iRead;
iRead = oZipStream.Read(aBytes, inOffSet, 2048);
inOffSet += iRead;
}
strmMem.Seek(0, SeekOrigin.Begin);
msgPart.Data = strmMem;
IBaseMessage outMsg;
outMsg = pContext.GetMessageFactory().CreateMessage();
outMsg.AddPart("Body",
pContext.GetMessageFactory().CreateMessagePart(), true);
outMsg.BodyPart.Data = strmMem;
qOutputMsgs.Enqueue(outMsg);
sEntry = oZipStream.GetNextEntry();
}
Note that to create an output message to BizTalk, we're using the context object that we received as a parameter. This context accesses BizTalk's MessageFactory
, that allows us to create messages to BizTalk.
Returning the messages to BizTalk
Now, we need to implement the GetNextMessage
method. This method is called for each message we'll return to BizTalk. When there are no more messages, the method will return null
. Since our output messages are available in our Queue
, we'll use the Dequeue
method to return them. When the Queue
is empty, we'll return null
to finish the pipeline component process. The code below implements this functionality:
if(qOutputMsgs.Count>0)
return (IBaseMessage)qOutputMsgs.Dequeue();
else
return null;
Compiling the project
Before we compile our project, we should change some of the properties of the project. The first project we need to change is the output path of the assembly. In order to work with BizTalk, custom pipeline components should be placed in a specific BizTalk folder (the \Program Files\Microsoft BizTalk Server 2004\Pipeline Components). The properties of the project should be as shown in the picture below:
After updating the project properties, build it.
Creating a test project
Now that out pipeline component is ready, we need to test it. To do this, we'll create a new BizTalk project that will use our custom pipeline component. Add a new BizTalk Server project in our solution, and name it PipelineTest.
In this project, add a new "Receive Pipeline" item and name in UnzipTest.btp. Open the UnzipTest.btp and check that the toolbox doesn't show our custom pipeline component. To add the component to the toolbox, select Add/Remove Items (right click on the toolbox). A new window will show up (like in the picture below). In this window, select UnzipDisassemblerComponent.
Now that we have our component in the toolbox, drag it to the disassemble stage of the pipeline. Below this component, drag a XmlDisassembler
as well. Your pipeline should have the components as shown below:
Debugging the pipeline component
Now that our component is ready, we need to debug it. To debug a pipeline component, we'll need to use a tool that comes with BizTalk Server. This tool is called Pipeline.EXE, at it simulates the execution of a pipeline component for an input message.
Open the project properties of the pipeline component project, and select the Debugging tab. In Debug mode, select "Program", and click the "Apply" button. In the property "Start Application", use the path to the pipeline.exe tool. The most common path is shown below:
C:\Program Files\Microsoft BizTalk Server
2004\SDK\Utilities\PipelineTools\Pipeline.exe
In the command line, use the arguments below:
C:\Labs\Work\BSCPipeline\UnzipTest.btp -d
C:\Order.zip -c -m C:\%messageid%.xml
The first parameter represents the path to the pipeline file that we created in the PipelineTest project (you need to provide the complete path for this to work). The -d parameter represents the input document we'll use. In our example, the input document is a .zip file. The last parameters -c and -m, represent the output options (-c represents that the output should be shown in the console, and -m shows the output path of the generated messages).
After setting these parameters, set some breakpoints in the project and run it, you should see the complete steps of the execution of the pipeline.
Conclusion
As you can see, the creation of a custom pipeline component for BizTalk Server 2004 is a simple task, it just requires some extra coding time. Hope you like the article! :)