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

Change Namespace of a Message in a Custom Pipeline Component

5.00/5 (1 vote)
14 Jan 2011CPOL5 min read 40.8K   457  
How to change the Namespace of a Message in a Custom Pipeline Component

Introduction

In this article, I describe in detail how to develop a custom pipeline component to change the namespace of a message on BizTalk 2006 R2. In BizTalk 2009, there are existing components to add and remove namespaces.

This component can be used in the decode stage in a receive pipeline. A public property allows the user to type a new namespace which will be added to the root node or replace an existing namespace in the XML message.

The Code

This project is created as a new Class Library in Visual Studio and signed with a strong name key file.

One reference is added, Microsoft.BizTalk.Pipeline.dll. This can be found in the folder ”Microsoft BizTalk Server 2006” in the location of the BTS installation.

The class in this project is renamed to ChangeNsDecoder 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;

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 decoder 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 decoder 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.ChangeNsDecoder
{
    [ComponentCategory(CategoryTypes.CATID_PipelineComponent)]
    [ComponentCategory(CategoryTypes.CATID_Decoder)]
    [System.Runtime.InteropServices.Guid("5136B07D-DC44-4481-B3A8-35DA712CC538")]
    public class ChangeNsDecoder : 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 change the namespace of a message";
private const string _name = "ChangeNsDecoder";
private const string _version = "1.0.0.0";

// Component description
public string Description
{
    get { return _description; }
}
// Component name
public string Name
{
    get { return _name; }
}
// Component version
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();
// Icon associated with this component   
public IntPtr Icon
{
    get { return _icon; }
}
// Verifies that all the configuration properties are set correctly        
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 (in this case, the new namespace) for the pipeline component. When used in a receive pipeline, this component allows the user to type a new namespace in a property in the receive location pipeline configuration in the BizTalk Administration Console.

C#
#region IPersistPropertyBag
private string _newNamespace;
public string NewNamespace
{
    get { return _newNamespace; }
    set { _newNamespace = value; }
}
public void GetClassID(out Guid classID)
{
    classID = new Guid("3A9B8F47-60A0-43F0-AFE2-73D08511668D");
}
public void InitNew()
{
}
public void Load(IPropertyBag propertyBag, int errorLog)
{
    object val = null;
    try
    {
        propertyBag.Read("NewNamespace", out val, 0);
    }
    catch(ArgumentException)
    {
    }
    catch(Exception ex)
    {
        throw new ApplicationException("Error reading PropertyBag: " + ex.Message);
    }
    if (val != null)
        _newNamespace = (string)val;
    else
        _newNamespace = "http://AnonymousURL";
}
public void Save(IPropertyBag propertyBag, bool clearDirty, bool saveAllProperties)
{
    object val = (object)_newNamespace;
    propertyBag.Write("NewNamespace", ref val);
}
#endregion

The NewNamespace property is used to configure the new namespace at design time.

The Load method reads the value from the PropertyBag into the NewNamespace property. I experienced an error message when adding the component to a decode 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 saves the value from the NewNamespace 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 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)
        {
            using (XmlReader reader = XmlReader.Create(originalStream))
	   {
	       XmlWriterSettings ws = new XmlWriterSettings();
	       ws.Indent = true;
	       ws.ConformanceLevel = ConformanceLevel.Auto;

	       MemoryStream outputStream = new MemoryStream();

	       using (XmlWriter writer = XmlWriter.Create(outputStream, ws))
	       {
	           reader.Read();
		  if (reader.NodeType == XmlNodeType.XmlDeclaration)
		  {
		      writer.WriteStartDocument();
		      reader.Read();
		   }

		   // Root Node
		   if (reader.NodeType == XmlNodeType.Element)
		       writer.WriteStartElement(reader.Name, _newNamespace);

		   reader.Read();
	            while (reader.NodeType == XmlNodeType.Element)
		   {
		       writer.WriteNode(reader, true);
		   }
	        }
	        outputStream.Position = 0;
	        bodyPart.Data = outputStream;
	    }
        }
    }
    return pInMsg;
}
#endregion
}// Ends the class
}// Ends the namespace

In the code above, I start by checking if the input stream contains some data and then I create an XmlReader to read from the original stream and an XmlWriter to write to an output stream.

When the writer is created, the line reader.Read() reads the first node in the XML message. If the first node is an XML declaration, then the writer writes an XML declaration to the output stream and the reader reads the next node.

The reader is now pointing to the root node and here is the place to change the namespace. The writer does this by writing a new element to the output stream. This will be the root node in the output message. The line writer.WriteStartElement(reader.Name, _newNamespace) does this. The first argument is the name of the element (the root node) and the second is the new namespace. Remember that _newNamespace is defined in the IPersistPropertyBag region.

Next, the line reader.read() reads the first node within the root node. Inside the while loop, the writer writes all the nodes within the root node and all their contents to the output stream. The output stream is now completed and contains the same data as the input stream.

To finish the code, I set the position of the output stream to the beginning of the stream and assign the output stream to the Data property of the bodyPart of the message. Then I return the message.

Install and Test the Component

To use this component, the project needs to be built and the DLL can be copied to the folder ”Microsoft BizTalk Server 2006\Pipeline Components” in the location of the BTS installation. (It can however be copied anywhere.)

In a BTS receive pipeline project, this component DLL must be added to the toolbar and used in the decode stage. The value of the NewNamespace property can be set at pipeline design time. After the pipeline is deployed, this property can also be set in the configuration of the pipeline in a receive location 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 internet.

History

  • 14th January, 2011: Initial post

License

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