user836968
user836968

Reputation:

How to add custom namespaces to an incoming XML message?

What's the recommended way of adding namespaces to an incoming XML message which needs to be debatched? In order to debatch the incoming XML message I'm using an envelope as well as a "normal" message xsd schema which both have a target namespace. The incoming XML message doesn't have any namespaces attached in the first place.

Thank you

using System;
using System.Linq;
using System.Xml.Linq;

public class AddNamespaceRcvPipelineComponent
{
    static public void Main ()
    {
        XElement xDoc = XElement.Load(@"test.xml");

        XNamespace ns1 = "Namespace1";
        XNamespace ns2 = "Namespace2";

        foreach (XElement el in xDoc.DescendantsAndSelf("ORDERS"))
        {
            // el.Add(new XAttribute(XNamespace.Xmlns + "ns1", "Namespace1"));
            el.Name = ns1 + el.Name.LocalName;
        }

        foreach (XElement el in xDoc.DescendantsAndSelf("ORDER"))
        {
            // el.Add(new XAttribute(XNamespace.Xmlns + "ns2", "Namespace2"));
            el.Name = ns2 + el.Name.LocalName;
        }

        // TODO: Strip empty namespaces ...

        Console.WriteLine(xDoc);
    }
}

Upvotes: 1

Views: 3279

Answers (2)

Maxime Labelle
Maxime Labelle

Reputation: 3639

The canonical way to solve your problem is indeed to use a custom Pipeline Component in the Decode Stage of the incoming pipeline.

Here are two ways to get this working efficiently.

Using the BizTalk ESB Tookit Pipeline Component

The BizTalk ESB Toolkit 2.1 has a very powerful set of pipeline components to manipulate XML namespaces in incoming messages. This pipeline is available in the Microsoft.Practices.ESB.Namespace.PipelineComponents.dll assembly, made available to the Visual Studio toolbox when installed.

Previous versions of these components are documented on MSDN. Although this documentation is quite outdated, I think nothing much has changed in the new version. I nonetheless suggest you refer to various forum questions for proper usage and reference.

Building a Simple AddXmlNamespace Pipeline Component

If you don't want to or cannot install BizTalk ESB Toolkit in your environment, I suggest building a simplified version of the component yourself. It's not hard at all, thanks to a builtin class in the BizTalk runtime that you can leverage.

I'm showing the code required below, because the code you showed in your question does not adhere to proper streaming-enabled pipeline components. Please, note that the code shown hereafter only deals with adding an XML namespace on the original root tag, if one is not already present.

First, you'll need to build a simple System.IO.Stream-derived class that handles adding an XML namespace and prefix on the root tag of the original document.

In order to support streaming, the code leverages the Microsoft.BizTalk.Streaming.XmlTranslatorStream class. This class exhibits a SAX-like interface, whereby overrides on your implementation are invoked at various points of the XML parsing stage. All of this is performed while maintaining full support for streaming arbitrary large documents.

This is the code :

using Microsoft.BizTalk.Streaming;

public class AddXmlNamespaceStream : XmlTranslatorStream
{
    private String namespace_;
    private int level_ = 0; // hierarchy level

    public AddXmlNamespaceStream(Stream stream, String @namespace)
        : base(XmlReader.Create(stream))
    {
        namespace_ = @namespace;
    }

    #region XmlTranslatorStream Overrides

    protected override void TranslateStartElement(string prefix, string localName, string nsURI)
    {
        if (level_++ != 0)
        {
            base.TranslateStartElement(prefix, localName, nsURI);
            return;
        }

        if (String.IsNullOrEmpty(nsURI))
        {
            nsURI = namespace_;
            if (String.IsNullOrEmpty(prefix))
                prefix = "__bts_ns0__";
        }

        base.TranslateStartElement(prefix, localName, nsURI);
    }

    protected override void TranslateEndElement(bool full)
    {
        if (level_-- != 0)
        {
            base.TranslateEndElement(full);
            return;
        }

        base.TranslateEndElement(full);
    }

    #endregion
}

You'll notice that this class overrides both the TranslateStartElement and TranslateEndElement methods but only deals with elements at the first level of hierarchy - which is the root tag. All other elements are processed according the default do-nothing-special behavior provided by the base class.

As for the pipeline component itself, I will only show its Execute method here, since you probably have already setup all the required boilerplate infrastructure code. If that's not the case, please refer to the blog posts at the following location, starting from the bottom.

Here it is:

public class AddXmlNamespace : ..., IComponent
{
    #region Design-Time Properties

    public String Namespace { get; set; }

    #endregion

    #region IComponent Implementation

    public IBaseMessage Execute(IPipelineContext pContext, IBaseMessage pInMsg)
    {
        var stream = new AddXmlNamespaceStream( 
            pInMsg.BodyPart.GetOriginalDataStream()
            , Namespace);

        pInMsg.BodyPart.Data = stream;
        pContext.ResourceTracker.AddResource(stream);

        return pInMsg;
    }

    #endregion

    ...
}

As you see, the only action taken by the Execute method is to wrap the original stream in an instance of the new AddXmlNamespace stream class, and wire it up so that it replaces the incoming message stream.

Hope this helps.

Upvotes: 2

Nigel B
Nigel B

Reputation: 3597

The ESB toolkit has a pipeline component which does this.

Upvotes: 0

Related Questions