Converting String to XLangMessage (Streaming)

Often when you want to create a message in an orchestration, the option of using an XMLDocument is chosen for this. However, this option can be a serious performance hit, as your entire message will be loaded into memory, and can become as large as 10 times it’s original size. To avoid these performance hits, I have created 2 small helper methods, which allow you to instead use BizTalk’s native messages instead of an XMLDocument, and create the message in a streaming way. Using these, you can simply create a message from a string (and the other way around).

String to XLangMessage

Let’s start with the method to create an XLangMessage from a string. Do keep in mind, that you have to pass in valid XML message text, or an error will be thrown.

/// <summary>
/// Create an XLANGMessafe from a string.
/// </summary>
public static XLANGMessage CreateXLANGMessageFromString(string xmlMessage, string messagePart1Name, string messagePart2Name, string messagePart3Name)
{
	// Create a VirtualStream for memory optimization
	int bufferSize = 640;
	int thresholdSize = 1048576; // 1MB
	var stream = new VirtualStream(bufferSize, thresholdSize);
 
	// Make sure booleans are in lowercase
	xmlMessage = xmlMessage.Replace("False", "false").Replace("True", "true");
 
	// The name of the root node
	var rootNodeName = String.Empty;
 
	// Create a reader which will be used to read through the message
	using (var reader = new XmlTextReader(new StringReader(xmlMessage)))
	{
		// Create a writer used to write the outgoing message
		using (var writer = XmlWriter.Create(stream, new XmlWriterSettings { ConformanceLevel = ConformanceLevel.Auto, Encoding = Encoding.UTF8 }))
		{
			// Boolean indicating if the root node name has been set
			var rootNodeNameSet = false;
 
			// Read through the message
			while (reader.Read())
			{
				// Check if we need to set the root node name here
				if (!rootNodeNameSet && reader.NodeType == XmlNodeType.Element)
				{
					// Set the root node name
					rootNodeName = reader.LocalName;
					rootNodeNameSet = true;
				}
 
				// Write the current element
				ProcessMessage.WriteShallowNode(reader, writer);
			}
		}
	}
 
	// Reset the stream
	stream.Seek(0, SeekOrigin.Begin);
 
	// Create a CustomBTXMessage, which will be used to create our XLANGMessage
	var customBTXMessage = new CustomBTXMessage(rootNodeName, Service.RootService.XlangStore.OwningContext);
 
	// Add message part
	customBTXMessage.AddPart(String.Empty, messagePart1Name);
 
	// Load the message we just created
	customBTXMessage[0].LoadFrom(stream);
 
	// Add extra message parts if needed
	if (!String.IsNullOrWhiteSpace(messagePart2Name))
	{
		// Add message part
		customBTXMessage.AddPart(String.Empty, messagePart2Name);
	}
 
	// Add extra message parts if needed
	if (!String.IsNullOrWhiteSpace(messagePart3Name))
	{
		// Add message part
		customBTXMessage.AddPart(String.Empty, messagePart3Name);
	}
 
	// Return the XLANGMessage
	return customBTXMessage.GetMessageWrapperForUserCode();
}

Also, make sure you have the following using statements in your class:

using Microsoft.BizTalk.Streaming;
using Microsoft.XLANGs.BaseTypes;
using Microsoft.XLANGs.Core;

As you can see, we use a VirtualStream for performance reasons, combined with a XmlTextReader and XmlWriter. Once the message has been loaded into the XmlWriter’s stream, we use it to create a CustomBTXMessage, which is our own implementation of a BTXMessage. This way, we can directly assign our message to a message in a BizTalk Message Assignment shape. Our implementation of CustomBTXMessage is quite simple.

using Microsoft.BizTalk.XLANGs.BTXEngine;
using System;
using Microsoft.XLANGs.Core;
 
namespace DynaLean.CoreHelpers
{
    [Serializable]
    public sealed class CustomBTXMessage : BTXMessage
    {
        public CustomBTXMessage(string messageName, Context context)
            : base(messageName, context)
        {
            context.RefMessage(this);
        }
    }
}

While reading the XML, we use the WriteShallowNode method to write the XML into the XMLWriter’s stream. This method is as following.

/// <summary>
/// Write the node the reader is currently at to the writer.
/// </summary>
internal static void WriteShallowNode(XmlReader reader, XmlWriter writer)
{
	if (reader == null)
	{
		throw new ArgumentNullException("reader");
	}
 
	if (writer == null)
	{
		throw new ArgumentNullException("writer");
	}
 
	switch (reader.NodeType)
	{
		case XmlNodeType.Element:
			writer.WriteStartElement(reader.Prefix, reader.LocalName, reader.NamespaceURI);
			writer.WriteAttributes(reader, true);
 
			if (reader.IsEmptyElement)
			{
				writer.WriteEndElement();
			}
 
			break;
 
		case XmlNodeType.Text:
			writer.WriteString(reader.Value);
			break;
 
		case XmlNodeType.Whitespace:
		case XmlNodeType.SignificantWhitespace:
			writer.WriteWhitespace(reader.Value);
			break;
 
		case XmlNodeType.CDATA:
			writer.WriteCData(reader.Value);
			break;
 
		case XmlNodeType.EntityReference:
			writer.WriteEntityRef(reader.Name);
			break;
 
		case XmlNodeType.XmlDeclaration:
		case XmlNodeType.ProcessingInstruction:
			writer.WriteProcessingInstruction(reader.Name, reader.Value);
			break;
 
		case XmlNodeType.DocumentType:
			writer.WriteDocType(reader.Name, reader.GetAttribute("PUBLIC"), reader.GetAttribute("SYSTEM"), reader.Value);
			break;
 
		case XmlNodeType.Comment:
			writer.WriteComment(reader.Value);
			break;
 
		case XmlNodeType.EndElement:
			writer.WriteFullEndElement();
			break;
	}
}

One more thing to note, is we support multipart messages, but in the current method we only set the message of the first messagePart, and we optionally create 2 more. Of course, the method could be extended to load more messageparts at once.

XLangMessage to String

Now that we know how to convert a string to an XLangMessage, let’s look at how we can do the reverse. This can come in handy when you, for example, want to output the contents of message in a tracing statement, or if you need to contents for another reason.

/// <summary>
        /// Create a string from a XLANGMessage.
        /// </summary>
        public static string CreateStringFromXLANGMessage(XLANGMessage message, int index)
        {
            // The string to be returned
            var toReturn = String.Empty;
 
            try
            {
                // Get the message part as a Stream and create a reader which will read in the stream
                using (StreamReader reader = new StreamReader(message[index].RetrieveAs(typeof(Stream)) as Stream))
                {
                    // Read in the message
                    toReturn = reader.ReadToEnd();
                }
            }
            finally
            {
                // Clean memory
                message.Dispose();
            }
 
            // Return the result
            return toReturn;
        }

Once again, we use a streaming approach to optimize performance. Other then that, it’s a very straightforward implementation, you just need to make sure you provide the correct index for the messagepart you want to retrieve in the message.

Finally

I hope this post can help someone out in building high performing solutions as it did me. Special thanks go out to Paolo Salvatori and Mark Fussell for inspiration and some sample code.

Leave a Reply

Your email address will not be published. Required fields are marked *