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

Unzip Files in a Custom Pipeline Component

4.25/5 (3 votes)
1 Feb 2011CPOL5 min read 46K   1.1K  
How to unzip files in a custom receive pipeline component

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:

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

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

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

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

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

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

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

License

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