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:
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.
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.
#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";
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.
#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 (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.
#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.
#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();
}
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
}
}
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