Friday, October 25, 2013

Custom ZipReceivePipeline to Unzip Multi-Type Messages

Many times there are requirements where the default pipelines provided by BizTalk are not helpful , but we all know that BizTalk is capable of dealing with any type of message.

One such scenario is accepting the incoming message in zipped format which has XML files as well as image files, in this case we need to create a custom pipeline along with a custom disassembler which will unzip the incoming message and split the message into individual XML and Image files. Also a property should be promoted which is used for creating subscriptions and routing .

When we develop a custom pipeline component we actually need to implement the Interfaces IBaseComponent, IComponentUI and IPersistPropertyBag(optional)  and  IComponent. Depending on the requirement, custom logic is added to the required area.

Note: When creating Assemble or Disassemble component , IComponent interface is not implemented instead IAssemblerComponent  interface and IDisassemblerComponent interface are implemented.

Wherein, this interfaces have properties and methods defined. Below are the interfaces showing the definitions it holds.

IComponent : It holds only one method i.e. Execute which returns message of type IBaseMessage which is expected by BizTalk Engine.
#region Assembly Microsoft.BizTalk.Pipeline.dll, v2.0.50727
// C:\Program Files\Microsoft BizTalk Server 2010\Microsoft.BizTalk.Pipeline.dll
#endregion

using Microsoft.BizTalk.Message.Interop;
using System.Runtime.InteropServices;

namespace Microsoft.BizTalk.Component.Interop
{
    [Guid("05ac2001-2ed5-41f0-a961-2838a1836a22")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IComponent
    {
        IBaseMessage Execute(IPipelineContext pContext, IBaseMessage pInMsg);
    }
}



IComponentUI: It has definition of one property Icon which is used to represent Icon associated with the component and one method  Validate which is used to validate the properties set in component.
#region Assembly Microsoft.BizTalk.Pipeline.dll, v2.0.50727
// C:\Program Files\Microsoft BizTalk Server 2010\Microsoft.BizTalk.Pipeline.dll
#endregion

using System;
using System.Collections;
using System.Runtime.InteropServices;

namespace Microsoft.BizTalk.Component.Interop
{
    [Guid("05ac2106-2ed5-41f0-a961-2838a1836a22")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IComponentUI
    {
        IntPtr Icon { get; }

        IEnumerator Validate(object projectSystem);
    }
}



IBaseComponent:It has three properties defined which defines the component properties i.e.Description,Name and Version which is visible in Properties tab of the component.
#region Assembly Microsoft.BizTalk.Pipeline.dll, v2.0.50727
// C:\Program Files\Microsoft BizTalk Server 2010\Microsoft.BizTalk.Pipeline.dll
#endregion

using System;
using System.Runtime.InteropServices;

namespace Microsoft.BizTalk.Component.Interop
{
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("05ac2000-2ed5-41f0-a961-2838a1836a22")]
    public interface IBaseComponent
    {
        string Description { get; }
        string Name { get; }
        string Version { get; }
    }
}



IPersistPropertyBag: It has four methods defined which are used to set the properties of the component.
#region Assembly Microsoft.BizTalk.Pipeline.dll, v2.0.50727
// C:\Program Files\Microsoft BizTalk Server 2010\Microsoft.BizTalk.Pipeline.dll
#endregion

using System;
using System.Runtime.InteropServices;

namespace Microsoft.BizTalk.Component.Interop
{
    [Guid("37D84F60-42CB-11CE-8135-00AA004BB851")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IPersistPropertyBag
    {
        void GetClassID(out Guid classID);
        void InitNew();
        void Load(IPropertyBag propertyBag, int errorLog);
        void Save(IPropertyBag propertyBag, bool clearDirty, bool saveAllProperties);
    }
}



IAssemblerComponent: It has two methods defined AddDocument and Assemble.
#region Assembly Microsoft.BizTalk.Pipeline.dll, v2.0.50727
// C:\Program Files\Microsoft BizTalk Server 2010\Microsoft.BizTalk.Pipeline.dll
#endregion

using Microsoft.BizTalk.Message.Interop;
using System;
using System.Runtime.InteropServices;

namespace Microsoft.BizTalk.Component.Interop
{
    [Guid("05ac2004-2ed5-41f0-a961-2838a1836a22")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IAssemblerComponent
    {
        void AddDocument(IPipelineContext pContext, IBaseMessage pInMsg);
        IBaseMessage Assemble(IPipelineContext pContext);
    }
}



IDisassemblerComponent: It has two methods defined , where Disassemble method is used to hold the custom logic and GetNext method is used to pass Message to BizTalk engine of IBaseMessageType.
#region Assembly Microsoft.BizTalk.Pipeline.dll, v2.0.50727
// C:\Program Files\Microsoft BizTalk Server 2010\Microsoft.BizTalk.Pipeline.dll
#endregion

using Microsoft.BizTalk.Message.Interop;
using System;
using System.Runtime.InteropServices;

namespace Microsoft.BizTalk.Component.Interop
{
    [Guid("05ac2003-2ed5-41f0-a961-2838a1836a22")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IDisassemblerComponent
    {
        void Disassemble(IPipelineContext pContext, IBaseMessage pInMsg);
        IBaseMessage GetNext(IPipelineContext pContext);
    }
}



Developing custom Disassembler component:
In this case as we are developing disassembler component which will unzip the message and promote the property which will hold the extension of files which are unziped so following interfaces are to be implemented  IBaseComponent, IComponentUI, IPersistPropertyBag and IDisassemblerComponent.

And in order to implement this interfaces , we need to use namespaces "Microsoft.BizTalk.Message.Interop","Microsoft.BizTalk.Component.Interop" which belong to "Microsoft.BizTalk.Pipeline.dll" assembly can be found at C:\Program Files\Microsoft BizTalk Server 2010\Microsoft.BizTalk.Pipeline.dll  and also we need to use "Ionic.Zip" namespace which belongs to "Iconic.Zip" assembly can be downloaded from http://dotnetzip.codeplex.com/.There are many other Zip libraries available also.

After creating new C# project , we need to add reference to those assemblies and sign it with strong name .


Next is to add new class and implement the interfaces and along-with custom logic to unzip messages and promoting the extension of the unzipped files which can be used to route messages based on the extension.

To indicate that the component is custom pipeline component and can only be used in Disassemble stage, couple of attributes are added to the class.

GUID (global unique identifier) is to create a unique identity for an entity(component) have look at following post: Adding GUID generator to Visual Studio 2010 Tools.

To add a custom context property and promoting it in pipeline component , have look at following post: Promoting custom context property using Pipeline Component


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Microsoft.BizTalk.Message.Interop;
using Microsoft.BizTalk.Component.Interop;
using Ionic.Zip;

namespace UnzipMessages
{
    //Attributes of class 
    [ComponentCategory(CategoryTypes.CATID_PipelineComponent)]
    [ComponentCategory(CategoryTypes.CATID_DisassemblingParser)]

    //Generate unique identifier
    [System.Runtime.InteropServices.Guid("9ABA4232-1F8E-4a45-B12D-9BE50160464B")]
    public class UnzipMessageDisassembler:IBaseComponent,
                                          IComponentUI,
                                          IDisassemblerComponent,
                                          IPersistPropertyBag,
                                          
    {
        //Implementing Interfaces
        #region IBaseComponent

        private const string description = "UnzipMessages pipeline component";
        private const string name = "UnzipMessageDisaasembler";
        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


        #region IComponentUI

        private IntPtr icon = new IntPtr();

        public IntPtr Icon
        {
            get
            {
                return icon;
            }
        }

        public System.Collections.IEnumerator Validate(object projectsystem)
        {
            return null;
        }

        #endregion


        #region IPersistPropertyBag
        public void GetClassID(out Guid classid)
        {
            classid = new System.Guid("9ABA4232-1F8E-4a45-B12D-9BE50160464B");
        }

        public void InitNew()
        {
       
        }
        public void Load(IPropertyBag propertyBag, int errorlog)
        {
       
        }
        public void Save(IPropertyBag propertyBag, bool clearDirty, bool saveAllProperties)
        {
       
        }
        #endregion

        #region IDisassemblerComponent
        // This component will read the zipped input message as a stream and with the help         //of Zip library the message will unzipped and stored in the Queue.

        private System.Collections.Queue OutFiles = new System.Collections.Queue();

        public void Disassemble(IPipelineContext pContext, IBaseMessage pInMsg)
        {
            IBaseMessagePart msgBodyPart = pInMsg.BodyPart;
            if (msgBodyPart != null)
            {
                Stream msgBodyPartStream = msgBodyPart.GetOriginalDataStream();
                if (msgBodyPartStream != null)
                {
                    using (ZipInputStream zipInputStream = new ZipInputStream(msgBodyPartStream))
                    {
                        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);
                            }

                            //Creating outMessage
                            IBaseMessage outMessage;
                            outMessage = pContext.GetMessageFactory().CreateMessage();
                            outMessage.AddPart("Body", pContext.GetMessageFactory().CreateMessagePart(), true);
                            memStream.Position = 0;
                            outMessage.BodyPart.Data = memStream;

                            //Creating custom context property to hold extension of file
                            string extension = string.Empty;
                            extension = entry.FileName.Substring(entry.FileName.IndexOf("."));
                            
                            //Promoting the custom property
                            pInMsg.Context.Promote("Extension", "https://DemoZip.ZipMessageProperties", extension);
                          
                            outMessage.Context = PipelineUtil.CloneMessageContext(pInMsg.Context);
                               
                            //Add outMessage to queue
                            OutFiles.Enqueue(outMessage);

                            entry = zipInputStream.GetNextEntry();
                       }                      
                       
                   }
                    
                 }
               
              
                }
           
            }  
     
        public IBaseMessage GetNext(IPipelineContext pContext)
        {
            if (OutFiles.Count > 0)
                return (IBaseMessage)OutFiles.Dequeue();
            else
                return null;
       
        }
        #endregion
    }
}




Now build it and add it to gac.


Next is to copy the dll at location : C:\Program Files\Microsoft BizTalk Server 2010\Pipeline Components
Doing this makes this component available in the Pipeline Designer Tool Box.


Add new Receive pipeline and reset Toolbox - this will refresh Toolbox and custom component will be reflected. Drag and drop it in Disassemble stage. Build the project and deploy it.




ZipReceivePipeline is ready to use now.

See how to process Zip Files : BizTalk Server 2010: Processing Zip Message Having Multiple Type Files

Download Sample: BizTalk Server 2010: Processing Zip Message Having Multiple Type Files Sample

Will keep on posting as an when I find something to share!!!!!!!!!!!!

No comments:

Post a Comment