Introduction
In this article, I describe how to develop a custom pipeline component to zip messages in the encode stage in a BizTalk send pipeline.
This component has two properties which allow the user to type a password to the zip file and an extension to the entry in the zipped file. The result from a send pipeline with this component will be a zip file containing the message with the typed extension.
Much of this code is basic when creating a custom pipeline component and a wizard can easily implement this. The most important code is found in the IComponent
region in 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 ZipEncoder
and the code starts by adding the following namespaces:
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;
using Microsoft.BizTalk.Message.Interop;
using Microsoft.BizTalk.Component.Interop;
using Ionic.Zip;
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 encode 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. An encoder pipeline component needs to implement the interfaces IBaseComponent
, IComponentUI
and IComponent
. In addition, the class below implements the interface IPersistPropertyBag
, but this is optional.
namespace Stm.ZipEncoder
{
[ComponentCategory(CategoryTypes.CATID_PipelineComponent)]
[ComponentCategory(CategoryTypes.CATID_Encoder)]
[System.Runtime.InteropServices.Guid("2cbc0379-c573-4506-9980-da69aad1496c")]
public class ZipEncoder : IBaseComponent, IComponentUI,
IComponent, 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 zip a message";
private const string _name = "ZipEncoder";
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 send pipeline, this component allows the user to type a password and entry extension in properties in the send port pipeline configuration in the BizTalk Administration Console.
#region IPersistPropertyBag
private string _password;
private string _entryExtension;
public string Password
{
get { return _password; }
set { _password = value; }
}
public string EntryExtension
{
get { return _entryExtension; }
set { _entryExtension = value; }
}
public void GetClassID(out Guid classID)
{
classID = new Guid("0c2cc78f-c88e-40db-a9ba-072c354af57f");
}
public void InitNew()
{
}
public void Load(IPropertyBag propertyBag, int errorLog)
{
object val1 = null;
object val2 = null;
try
{
propertyBag.Read("Password", out val1, 0);
propertyBag.Read("EntryExtension", out val2, 0);
}
catch (ArgumentException)
{
}
catch (Exception ex)
{
throw new ApplicationException("Error reading PropertyBag: " + ex.Message);
}
if (val1 != null)
_password = (string)val1;
if (val2 != null)
_entryExtension = (string)val2;
else
_entryExtension = "xml";
}
public void Save(IPropertyBag propertyBag, bool clearDirty, bool saveAllProperties)
{
object val1 = (object)_password;
object val2 = (object)_entryExtension;
propertyBag.Write("Password", ref val1);
propertyBag.Write("EntryExtension", ref val2);
}
#endregion
The Password
property is used to set a password to the zip archive. The EntryExtension
property is used to set an extension to the file in the archive.
The Load
method reads the values from the PropertyBag
into the properties. I experienced an error message when adding the component to an encode 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 values from the properties 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 IComponent
. In this project, it contains one method that executes the pipeline component to process the input message and get the resulting message.
#region IComponent
public IBaseMessage Execute(IPipelineContext pContext, IBaseMessage pInMsg)
{
IBaseMessagePart bodyPart = pInMsg.BodyPart;
if (bodyPart != null)
{
Stream originalStream = bodyPart.GetOriginalDataStream();
if (originalStream != null)
{
MemoryStream memStream = new MemoryStream();
using(ZipOutputStream zipOutputStream = new ZipOutputStream(memStream))
{
byte[] buffer = new Byte[1024];
zipOutputStream.CompressionLevel =
Ionic.Zlib.CompressionLevel.BestCompression;
if (_password != null)
zipOutputStream.Password = _password;
if (_entryExtension != null)
if (_entryExtension.Length > 0)
_entryExtension = "." + _entryExtension;
zipOutputStream.PutNextEntry(pInMsg.MessageID + _entryExtension);
int bytesRead = 1024;
while (bytesRead != 0)
{
bytesRead = originalStream.Read(buffer, 0, buffer.Length);
zipOutputStream.Write(buffer, 0, bytesRead);
}
}
byte[] buff = memStream.GetBuffer();
MemoryStream ms = new MemoryStream(buff);
ms.Position = 0;
pInMsg.BodyPart.Data = ms;
}
}
return pInMsg;
}
#endregion
}
}
The code above first checks if the input stream contains data. If so, a ZipOutputStream
object is created from the Ionic.Zip namespace to write zipped data to a MemoryStream
. The password from the PropertyBag
is set to the zipOutputStream
and then the zipOutputStream
begins to write a new zip file entry with the name created from the MessageID
and the EntryExtension
property.
In the while
loop, all the data is read from the original input stream and written to the MemoryStream
, memStream
, by the zipOutputStream
. When the loop is finished, memStream
contains the zipped data.
I experienced some problems when using the memStream
to return the message pInMsg
. I solved this by getting the data from the memStream
and put it into a buffer and then use this buffer to create a new MemoryStream
to be used when returning the data. The data is here compressed with a high ratio and I had no problems doing this.
Finally, the code returns the pInMsg
which now contains a zip archive.
Install and Test the Component
To use this component, the project needs to be built and in a BTS send pipeline project, this component DLL must be added to the toolbar and used in the encode stage. The values of the Password
and the EntryExtension
properties can be set at pipeline design time. After the pipeline is deployed, these properties can also be set in the configuration of the send 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 Unzip files in a custom pipeline component.
History
- 1st February, 2011: Initial post