woensdag 7 maart 2012

Remove Empty Nodes

We found out that Navision does not like empty nodes when receiving an XML message, as it places the empty elements in it's inbound table with empty strings, which means that validation for these elements will fail. To solve this, we had to remove the empty elements from the message that is being sent to Navision. A custom pipeline component was created for this, with the following Execute method.
/// <summary>
/// IComponent.Execute method is used to initiate the processing of the 
/// message in this pipeline component.
/// </summary>
/// <param name="pc">Pipeline context.</param>
/// <param name="inmsg">Input message.</param>
/// <returns>Original input message.</returns>
public Microsoft.BizTalk.Message.Interop.IBaseMessage Execute(
    Microsoft.BizTalk.Component.Interop.IPipelineContext pc, 
    Microsoft.BizTalk.Message.Interop.IBaseMessage inmsg)
{
    // Remove empty elements from the message
    return RemoveEmptyElements(inmsg);
}

maandag 5 maart 2012

BizTalk 2009 Archive Incoming Messages

At one of our customers we want to be able to archive all messages that come in to BizTalk 2009. I created a pipeline component that does this, where archiving can be turned on or off from the BizTalk Administrator Console. Here is the code to do this.

using System;
using System.IO;
using System.Text;
using System.ComponentModel;
using Microsoft.BizTalk.Component.Interop;
using Microsoft.BizTalk.Message.Interop;

namespace Company.BizTalk.PipelineComponents
{
    /// <summary>
    /// Archive an incoming message.
    /// </summary>
    [ComponentCategory(CategoryTypes.CATID_PipelineComponent)]
    [System.Runtime.InteropServices.Guid("CFBF459C-01FC-49B8-903B-2DE65A000FF6")]
    [ComponentCategory(CategoryTypes.CATID_Decoder)]
    public class ArchivingComponentIncoming : 
        Microsoft.BizTalk.Component.Interop.IComponent, IBaseComponent, 
        IPersistPropertyBag, IComponentUI
    {

        #region Properties

        /// <summary>
        /// Boolean indicating if we want to archive the messages coming through this 
        /// pipeline.
        /// </summary>
        private bool _doArchiving;

        /// <summary>
        /// Boolean indicating if we want to archive the messages coming through this 
        /// pipeline.
        /// </summary>
        public bool DoArchiving
        {
            get
            {
                return _doArchiving;
            }
            set
            {
                _doArchiving = value;
            }
        }

        /// <summary>
        /// Location where we want to archive the messages.
        /// </summary>
        private string _archivePath;

        /// <summary>
        /// Location where we want to archive the messages.
        /// </summary>
        public string ArchivePath
        {
            get
            {
                return _archivePath;
            }
            set
            {
                _archivePath = value;
            }
        }

        #endregion


        #region IBaseComponent members
        
        /// <summary>
        /// Name of the component
        /// </summary>
        [Browsable(false)]
        public string Name
        {
            get
            {
                return "ArchivingComponentIncoming";
            }
        }

        /// <summary>
        /// Version of the component
        /// </summary>
        [Browsable(false)]
        public string Version
        {
            get
            {
                return "1.0.0.0";
            }
        }

        /// <summary>
        /// Description of the component
        /// </summary>
        [Browsable(false)]
        public string Description
        {
            get
            {
                return "Component used to archive incoming messages.";
            }
        }

        #endregion

        #region IPersistPropertyBag members
        
        /// <summary>
        /// Gets class ID of component for usage from unmanaged code.
        /// </summary>
        /// <param name="classid">
        /// Class ID of the component
        /// </param>
        public void GetClassID(out System.Guid classid)
        {
            classid = new System.Guid("CFBF459C-01FC-49B8-903B-2DE65A000FF6");
        }

        /// <summary>
        /// Not needed.
        /// </summary>
        public void InitNew()
        {
        }

        /// <summary>
        /// Loads configuration properties for the component.
        /// </summary>
        /// <param name="pb">Configuration property bag.</param>
        /// <param name="errlog">Error status.</param>
        public virtual void Load(Microsoft.BizTalk.Component.Interop.IPropertyBag pb,
            int errlog)
        {
            object val = null;
            
            // Get configuration property for indication if we want to archive
            val = this.ReadPropertyBag(pb, "DoArchiving");
            
            if (val != null)
            {
                this._doArchiving = ((bool)(val));
            }
            
            // Get configuration property for path where we want to archive
            val = this.ReadPropertyBag(pb, "ArchivePath");
            
            if (val != null)
            {
                this._archivePath = ((string)(val));
            }
        }

        /// <summary>
        /// Saves the current component configuration into the property bag.
        /// </summary>
        /// <param name="pb">Configuration property bag.</param>
        /// <param name="fClearDirty">Not used.</param>
        /// <param name="fSaveAllProperties">Not used.</param>
        public virtual void Save(Microsoft.BizTalk.Component.Interop.IPropertyBag pb,
            bool fClearDirty, bool fSaveAllProperties)
        {
            this.WritePropertyBag(pb, "DoArchiving", this.DoArchiving);

            this.WritePropertyBag(pb, "ArchivePath", this.ArchivePath);
        }

        #region utility functionality
        
        /// <summary>
        /// Reads property value from property bag.
        /// </summary>
        /// <param name="pb">Property bag.</param>
        /// <param name="propName">Name of property.</param>
        /// <returns>Value of the property.</returns>
        private object ReadPropertyBag(
            Microsoft.BizTalk.Component.Interop.IPropertyBag pb, string propName)
        {
            object val = null;
            
            try
            {
                pb.Read(propName, out val, 0);
            }
            catch (System.ArgumentException)
            {
                return val;
            }
            catch (System.Exception e)
            {
                throw new System.ApplicationException(e.Message);
            }
            
            return val;
        }

        /// <summary>
        /// Writes property values into a property bag.
        /// </summary>
        /// <param name="pb">Property bag.</param>
        /// <param name="propName">Name of property.</param>
        /// <param name="val">Value of property.</param>
        private void WritePropertyBag(
            Microsoft.BizTalk.Component.Interop.IPropertyBag pb, string propName, 
            object val)
        {
            try
            {
                pb.Write(propName, ref val);
            }
            catch (System.Exception e)
            {
                throw new System.ApplicationException(e.Message);
            }
        }

        #endregion
        #endregion

        #region IComponentUI members
        /// <summary>
        /// Component icon to use in BizTalk Editor.
        /// </summary>
        [Browsable(false)]
        public IntPtr Icon
        {
            get
            {
                return IntPtr.Zero;
            }
        }

        /// <summary>
        /// The Validate method is called by the BizTalk Editor during the build of 
        /// a BizTalk project.
        /// </summary>
        /// <param name="obj">An Object containing the configuration properties.
        /// </param>
        /// <returns>The IEnumerator enables the caller to enumerate through a 
        /// collection of strings containing error messages. 
        /// These error messages appear as compiler error messages. 
        /// To report successful property validation, the method should return 
        /// an empty enumerator.</returns>
        public System.Collections.IEnumerator Validate(object obj)
        {
            return null;
        }

        #endregion

        #region IComponent members
        
        /// <summary>
        /// Implements IComponent.Execute method.
        /// </summary>
        /// <param name="pc">Pipeline context.</param>
        /// <param name="inmsg">Input message.</param>
        /// <returns>Original input message.</returns>
        /// <remarks>
        /// IComponent.Execute method is used to initiate the processing of the 
        /// message in this pipeline component.
        /// </remarks>
        public Microsoft.BizTalk.Message.Interop.IBaseMessage Execute(
            Microsoft.BizTalk.Component.Interop.IPipelineContext pc, 
            Microsoft.BizTalk.Message.Interop.IBaseMessage inmsg)
        {
            // Check if we want to archive the message
            if (_doArchiving)
            {
                // Get the message that was received
                IBaseMessage passedMessage = inmsg;

                // The name under which we want to archive the message
                string archiveFileName = null;

                // Get the interchange id from the message
                string interchangeID = (string)passedMessage.Context.Read(
                    "InterchangeID", 
                    "http://schemas.microsoft.com/BizTalk/2003/system-properties");

                // If the transport type if file or ftp, get the incoming filename 
                // to use as part of the archive filename (for easier identification)
                string filePath = null;

                // Get the type of adapter that was used for receiving the file
                string adapterType = (string)passedMessage.Context.Read(
                    "InboundTransportType", 
                    "http://schemas.microsoft.com/BizTalk/2003/system-properties");

                // Check if we received over a file adapter
                if (adapterType == "FILE")
                {
                    filePath = (string)passedMessage.Context.Read("ReceivedFileName",
                        "http://schemas.microsoft.com/BizTalk/2003/file-properties");
                }

                // Check if we received over a ftp adapter
                else if (adapterType == "FTP")
                {
                    filePath = (string)passedMessage.Context.Read("ReceivedFileName",
                        "http://schemas.microsoft.com/BizTalk/2003/ftp-properties");
                }

                // Set the name under which we want to archive the message
                archiveFileName = interchangeID + ".out";

                // Check if filePath was set
                if (filePath != null)
                {
                    // We ran into problems where the pipeline could not be executed,
                    // and the file just kept being archived
                    // To make sure this does not happen, we decided to never 
                    // archive the same message more then 100 times (still allowing 
                    // for multiple tests with the same message)
                    if (Directory.GetFiles(this._archivePath, String.Format("{0}*", 
                        Path.GetFileName(filePath))).Length > 100)
                    {
                        goto Finished;
                    }

                    // If filepath was set, add the incoming file name to the file 
                    // name under which we want to archive
                    archiveFileName = String.Format("{0}_{1}", 
                        Path.GetFileName(filePath), archiveFileName);
                }

                // Write the archive file
                WriteToFile(passedMessage, Path.Combine(this._archivePath, 
                    archiveFileName));
            }

            Finished:

            // Continue processing the message
            return inmsg;
        }

        /// <summary>
        /// This method is used to copy the contents from a memory stream to a
        /// filestream, saving the contents to a file.
        /// </summary>
        /// <param name="input">The memory stream.</param>
        /// <param name="output">The file stream.</param>
        protected void CopyStream(Stream input, Stream output)
        {
            // Set the buffer size for reading the stream
            int BUFFER_SIZE = 4096;

            // Create a buffer
            byte[] buffer = new byte[BUFFER_SIZE];

            // Integer indicating how many bytes we have read
            int bytesRead;
            
            try
            {
                // Read from the input stream
                bytesRead = input.Read(buffer, 0, BUFFER_SIZE);

                // Read all the bytes in the input stream
                while (bytesRead > 0)
                {
                    // Write the contents to the output stream
                    output.Write(buffer, 0, bytesRead);

                    // Go to the next byte
                    bytesRead = input.Read(buffer, 0, BUFFER_SIZE);
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                // Rewind input stream
                if (input.CanSeek)
                {
                    input.Position = 0;
                }
            }
        }

        /// <summary>
        /// Write the contents of the message to an archive file.
        /// </summary>
        /// <param name="message">The message that should be archived.</param>
        /// <param name="fileName">The filename for the archive message.</param>
        protected void WriteToFile(IBaseMessage message, string fileName)
        {
            // Get the body of the message
            Stream msgStream = message.BodyPart.GetOriginalDataStream();

            // Create a new file stream
            FileStream fileStream = null;

            try
            {
                // Open the archive file for writing
                fileStream = new FileStream(fileName, FileMode.OpenOrCreate);

                // Copy the data from the message to the archive file
                this.CopyStream(msgStream, fileStream);
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                // Close the file stream
                if (fileStream != null)
                {
                    fileStream.Close();
                }

                // Close the message
                if (msgStream.CanSeek)
                {
                    msgStream.Position = 0;
                }
            }
        }

        #endregion
    }
}

Introduction

Welcome to my blog. First, let me introduce myself. My name is Eldert Grootenboer, I live in the Netherlands, and I work as a BizTalk developer at a large IT consultancy company.
I have set this blog up to have a single point of reference for solutions for the problems I find in my daily work, and hopefully others will find these posts helpful as well. These solutions can be either things I come up with myself, things I learn from my collegues or things I have found on the internet (and often a combination of these).
If you have any questions, don't hesitate to contact me.