Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / productivity / biztalk

Zip Files in a Custom Pipeline Component

4.20/5 (2 votes)
1 Feb 2011CPOL4 min read 37K   1.1K  
How to zip files in a custom send pipeline component

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:

C#
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.

C#
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.

C#
#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.

C#
#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.

C#
#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.

C#
#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
}// Ends the class
}// Ends the namespace

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)