Dynamic message transformations are an
important technique often overlooked by developers. This feature allows
you to choose from a series of maps based on a parameter of the incoming
message and perform transformations inside orchestrations. A good
example of when this feature may come in handy is automating business
processes between a company and its partners located in different
countries. The documents arriving from the partners—for example,
purchase orders—may have a slightly different structure or different
field format depending on the standards of the country of origin.
Obviously, such documents have to be transformed to a generic form
before being submitted for further processing in the orchestration that
handles purchase orders. A straightforward solution would be to create a
separate port for each trading partner and configure each port to apply
a map performing country-specific transformations. Although this
approach is technically absolutely valid, it is not always an optimal
solution. If the number of trading partners keeps growing, adding more
and more identical ports for the only purpose of applying different maps
is not practical. Another compelling reason to transform messages
inside orchestrations is that orchestrations offer much greater
flexibility than pipelines to handle exceptions from failed maps.
1. Low-Volume Transformations
If the volume of messages requiring transformations
is relatively low, then all you have to do is to perform a few simple
steps as follows:
Build and deploy the project containing your maps.
Add
code into Expression shape to determine which map you have to apply.
Your source message schema should promote a field that you will pass to
the code. For the trading partners mentioned earlier, it can be, for
example, the CountryId. The code has to return a string in the following
format:
mapAssemblyName = "NameSpace.MapClassName,
AssemblyName,Version=..., Culture=...,PublicKeyToken=..."
Now add the following line of code to get the map type:
MyMapType = System.Type.GetType(mapAssemblyName);
where MyMapType is a variable of the System.Type type.
Add the following XLANG code into the Message Assignment shape:
transform(MyOutputMsg) = MyMapType(MyInputMsg);
where MyInputMessage is the source message and MyOutputMessage
is the destination message. This code performs the transformation and
generates the destination message ready for further processing.
Proceed with the rest of the business logic.
2. High-Volume Transformations
Sustaining a high volume of message transformations
requires an extra effort. Microsoft recommends writing a cache in user
code and then using the cache to retrieve maps before performing the
transformations. As the BizTalk Server 2009 documentation
states, "If you are not caching the maps, it is possible to see Common
Language Runtime (CLR) memory grow significantly. Dynamic mapping
requires that the .NET Runtime perform code access check which results
in a .NET Evidence object being placed in the Large Object Heap for each
transformation and this object is not disposed of until the
orchestration completes. Therefore, when there are a lot of these types
of transforms occurring simultaneously, you may see the memory usage
increase substantially which can also lead to the out of memory
exception." Unfortunately, the product documentation doesn't go further
than recommendations. Until Microsoft provides a solution that you can
reuse in your code, your choice is limited to writing a custom code and
using not officially supported internal infrastructure classes.
Listing 1
provides a helper class that addresses the memory issue by caching the
maps as the product documentation suggests. You can use this class to
perform message transformations in high-volume scenarios. Please note
the use of the TransformBase class from the Microsoft.XLANGs.BaseTypes
namespace. The TransformBase is the base class from which all map
classes inherit. You should be aware that since this class supports
internal BizTalk infrastructure and is not intended for use in your
code, you are not guaranteed that implementation of the class will not
change in the future.
Example 1. DynamicTransformsHelper Class
using System;
using System.Xml;
using System.Xml.Xsl;
using System.Xml.XPath;
using System.Reflection;
using System.IO;
using Microsoft.XLANGs.BaseTypes;
using System.Collections.Generic; namespace ProBiztalk.Samples.Utilities
{
[Serializable()]
public class DynamicTransforms
{
private static Dictionary<string, TransformBase> m_mapCache = null;
static DynamicTransforms()
{
m_mapCache = new Dictionary<string, TransformBase>();
}
public static XmlDocument Transform(XmlDocument xmlInputMessage,
string mapAssemblyName)
{
XmlDocument xmlOutputMessage = new XmlDocument();
// TransforBase is a base class
//which all BizTalk map classes inherit from
TransformBase mapClass = null;
//check if we already cached map class
if (m_mapCache.ContainsKey(mapAssemblyName) == false)
{
Type mapType = System.Type.GetType(mapAssemblyName);
mapClass = (TransformBase)System.Activator.CreateInstance(mapType);
m_mapCache.Add(mapAssemblyName, mapClass);
}
else
{
m_mapCache.TryGetValue(mapAssemblyName, out mapClass);
}
XslTransform transform = mapClass.Transform;
XsltArgumentList argList = mapClass.TransformArgs;
//perform transformation
xmlOutputMessage = InternalTransform(transform, argList,
xmlInputMessage);
return xmlOutputMessage;
}
/// <summary>
/// Performs XSL transformation
/// </summary>
/// <param name="xslt"></param>
/// <param name="args"></param>
/// <param name="inputMessage"><param>
/// <returns><returns>
private static XmlDocument InternalTransform(XslTransform xslt,
XsltArgumentList args,
XmlDocument inputMessage)
{
XmlNodeReader inputMessageRoot = new XmlNodeReader(inputMessage);
XPathDocument inputMessageXPath = new XPathDocument(inputMessageRoot);
MemoryStream stream = new MemoryStream();
XmlTextWriter writer = new XmlTextWriter(stream,
System.Text.Encoding.Unicode);
xslt.Transform(inputMessageXPath, args, writer, null);
stream.Flush();
stream.Seek(0, SeekOrigin.Begin);
//convert memory stream into XML document XmlTextReader outputReader;
XmlTextReader outputReader;
XmlDocument outputMessage;
outputReader = new XmlTextReader(stream);
outputMessage = new XmlDocument();
outputMessage.Load(outputReader);
return outputMessage;
}
}
}
|
Put this class into a separate project, build and GAC the assembly. Once it's done, the DynamicTransformsHelper class is ready for consumption. To transform messages using this class, you have to perform the following steps:
In your orchestrations project, add a reference to the assembly containing the DynamicTransformsHelper class.
Add
code into an Expression shape to determine which map you have to apply.
The code has to return a string in the following form:
mapAssemblyName = "NameSpace.MapClassName,
AssemblyName,Version=..., Culture=...,PublicKeyToken=..."
Into a Message Assignment shape add the following code to perform the transformation:
MyOutputMsg = ProBiztalk.Samples.Utilities.
DynamicTransforms.Transform (MyInputMsg,mapAssemblyName)
where MyOutputMsg is the destination message, MyInputMsg is the source message, and mapAssmeblyName is the map name that you determined in step 2.
Proceed with the rest of the business logic.