Introduction
In this article, I describe how to develop a custom pipeline component to unzip files in the disassemble stage in a BizTalk receive pipeline.
This component has one property which allows the user to type a password to be used when unzipping the files. All files in the received archive are unzipped.
Much of this code is basic when creating a custom pipeline component and a wizard can easily implement this. The essential code is found in the IDisassemblerComponent
region which is the last block of code in this article.
The Code
This project is created as a new Class Library in Visual Studio and signed with a strong name key file.
Two references are added, Microsoft.BizTalk.Pipeline.dll and Ionic.Zip.dll.
The class in this project is renamed to UnzipDisassembler
and the code starts by adding the following namespaces:
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.BizTalk.Message.Interop;
using Microsoft.BizTalk.Component.Interop;
using Ionic.Zip;
using System.IO;
In the code below, I add two attributes to the class. The first one tells that this is a pipeline component and the second one restricts the component to be used only in the disassemble stage in a pipeline.
The specific interfaces a pipeline component implements are what differentiate that pipeline component from another. Once a component has an interface implementation, it can be called by the BizTalk runtime. All components and component categories are found in the Microsoft.BizTalk.Component.Interop
namespace. A disassembler pipeline component needs to implement the interfaces IBaseComponent
, IComponentUI
and IDisassemblerComponent
. In addition, the class below implements the interface IPersistPropertyBag
, but this is optional.
namespace Stm.UnzipDisassembler
{
[ComponentCategory(CategoryTypes.CATID_PipelineComponent)]
[ComponentCategory(CategoryTypes.CATID_DisassemblingParser)]
[System.Runtime.InteropServices.Guid("90E45487-0AFC-49F5-81EB-737635678A17")]
public class UnzipDisassembler :
IBaseComponent, IComponentUI, IDisassemblerComponent, IPersistPropertyBag
{
Inside the class, I implement all the required properties and methods for all the interfaces.
I start with the IBaseComponent
interface. This interface provides properties that provide basic information about the components.
#region IBaseComponent
private const string _description = "Pipeline component used to unzip messages";
private const string _name = "UnzipDisassembler";
private const string _version = "1.0.0.0";
public string Description
{
get { return _description; }
}
public string Name
{
get { return _name; }
}
public string Version
{
get { return _version; }
}
#endregion
The interface IComponentUI
defines a method and property that are used within the Pipeline Designer environment. To keep it simple, I have not provided any code here.
#region IComponentUI
private IntPtr _icon = new IntPtr();
public IntPtr Icon
{
get { return _icon; }
}
public System.Collections.IEnumerator Validate(object projectSystem)
{
return null;
}
#endregion
Next I implement the IPersistPropertyBag
interface. This is done in order to store property information for the pipeline component. When used in a receive pipeline, this component allows the user to type a password in a property field in the receive pipeline configuration in the BizTalk Administration Console. This property is used if a password is required to unzip an archive.
#region IPersistPropertyBag
private string _password;
public string Password
{
get { return _password; }
set { _password = value; }
}
public void GetClassID(out Guid classID)
{
classID = new Guid("625BBF88-0F86-419A-83AE-B15A976A6715");
}
public void InitNew()
{
}
public void Load(IPropertyBag propertyBag, int errorLog)
{
object val1 = null;
try
{
propertyBag.Read("Password", out val1, 0);
}
catch (ArgumentException)
{
}
catch (Exception ex)
{
throw new ApplicationException("Error reading PropertyBag: " + ex.Message);
}
if (val1 != null)
_password = (string)val1;
}
public void Save(IPropertyBag propertyBag, bool clearDirty, bool saveAllProperties)
{
object val1 = (object)_password;
propertyBag.Write("Password", ref val1);
}
#endregion
The Password
property is used when a password is required to unzip the archive.
The Load
method reads the value from the PropertyBag
into the property. I experienced an error message when adding the component to a disassemble stage in a pipeline in the pipeline designer. However the component worked fine, but to avoid this error message, I made a catch
block to catch
the ArgumentException
.
The Save
method writes the value from the property into the PropertyBag
.
GetClassID
returns the components unique identified value.
InitNew
is used to initialize the object to be persisted in component properties. This is not required in this project.
The core interface is IDisassemblerComponent
. Disassemblers were intended to allow the pipeline to examine the incoming document and break it up into smaller documents.
IDisassemblerComponent
has two methods, Disassemble
and GetNext
. The BizTalk
runtime first calls the Disassemble
method and passes the original message and the pipeline context. It then calls the GetNext
method which returns new messages of type IBaseMessage
until the component decides that all messages are created and then it returns null
.
This component creates all messages in the Disassemble
method and enqueues them to a queue structure. The GetNext
method dequeues and returns a message each time it is called.
#region IDisassemblerComponent
private System.Collections.Queue _qOutMessages = new System.Collections.Queue();
public void Disassemble(IPipelineContext pContext,IBaseMessage pInMsg)
{
IBaseMessagePart bodyPart = pInMsg.BodyPart;
if(bodyPart != null)
{
Stream originalStream = bodyPart.GetOriginalDataStream();
if(originalStream != null)
{
using (ZipInputStream zipInputStream = new ZipInputStream(originalStream))
{
if (_password != null)
if (_password.Length > 0)
zipInputStream.Password = _password;
ZipEntry entry = zipInputStream.GetNextEntry();
while (entry != null)
{
MemoryStream memStream = new MemoryStream();
byte[] buffer = new Byte[1024];
int bytesRead = 1024;
while (bytesRead != 0)
{
bytesRead = zipInputStream.Read(buffer, 0, buffer.Length);
memStream.Write(buffer, 0, bytesRead);
}
IBaseMessage outMessage;
outMessage = pContext.GetMessageFactory().CreateMessage();
outMessage.AddPart
("Body", pContext.GetMessageFactory().CreateMessagePart(), true);
memStream.Position = 0;
outMessage.BodyPart.Data = memStream;
outMessage.Context = PipelineUtil.CloneMessageContext(pInMsg.Context);
_qOutMessages.Enqueue(outMessage);
entry = zipInputStream.GetNextEntry();
}
}
}
}
}
public IBaseMessage GetNext(IPipelineContext pContext)
{
if (_qOutMessages.Count > 0)
return (IBaseMessage)_qOutMessages.Dequeue();
else
return null;
}
#endregion
}
}
The code above first declares a private
queue for the messages. The Disassmeble
method checks if the input stream contains data and then creates a ZipInputStream
from the original input stream. A password can be set to the ZipInputStream
.
The code within the while
loop, which runs as long as the entry is different from null
, runs for each entry in the ZipInputStream
. A MemoryStream
, memStream
, is created and the data is read from the zipInputStream
and written into the MemoryStream
.
When the memStream
is completed, a message, outMessage
, is created based on the context of the current pipeline and incoming message. The data of the BodyPart
of this message is set to the memStream
. The message now contains one extracted entry from the incoming zipped message. The message is enqueued.
Each entry in the zipped message is now a new message in the queue. The GetNext
method dequeues and returns these messages.
Install and Test the Component
To use this component, the project needs to be built and in a BTS receive pipeline project, this component DLL must be added to the toolbar and used in the disassemble stage. The value of the Password
property can be set at pipeline design time. After the pipeline is deployed, this property can also be set in the configuration of the receive pipeline in the BTS Administration Console.
TIP: If the component needs to be changed and built again after it is used in a pipeline, there can be a problem when building the component because the DLL file can be used by another process. To solve this, I use the WhoLockedMe
application to see what processes lock the file and then I can kill these processes. WhoLockedMe
is free and can easily be Googled and downloaded from the internet.
See also Zip files in a custom pipeline component.
History
- 1st February, 2011: Initial post