Recently, I shed some light on how Maps are compiled to .NET assemblies. Perhaps one of the most asked question on microsoft.public.biztalk.* is "Calling a map from C# or VB.NET?". This post attempts to answer that question and clarifies a few things.
It is possible to run a map produced by the BizTalk 2004 mapper outside of BizTalk and it is even possible; under certain conditions; to run the map on a machine that does not have BizTalk installed. The steps required for using a map outside of BizTalk are outlined below:
In the list of steps above 3, parts of 4 and 5 have nothing to do with BizTalk: these are just plain .NET programming. Creating the XsltArgumentList requires us to understand how the mapper saves functoids.
Extracting the XSL and the extension objects (if any) can be achieved by at least three different methods:
The only speed bump is the format of the Extension Object XML document. I have extracted the extension associated with the map Scriptor_CallExternalAssembly from the "ExtendingMapper" SDK sample and formatted it:
<ExtensionObjects> <ExtensionObject Namespace="http://schemas.microsoft.com/BizTalk/2003/ScriptNS0" AssemblyName="Microsoft.Samples.BizTalk.ExtendingMapper.MapperClassLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f2aaad746c3d94f5" ClassName="Microsoft.Samples.BizTalk.ExtendingMapper.MapperHelper" /> </ExtensionObjects>
Creating the extension objects is now very simple. For each "ExtensionObject" node, we need to load the assembly, create an instance of the given class and add the object along with its namespace to the XsltArgumentList. Of course, the map will only run if all needed assemblies are available. This is true for custom assemblies as well as out of the box functoids. The code below does exactly this and the full solution can be downloaded here:
using System; using System.Reflection; using System.Xml; using System.Xml.Xsl; using System.IO; using System.Text; namespace MapReuser { /// <summary> /// Transforms XML instances using a BizTalk map. /// </summary> public class BizTalkMap { /// <summary> /// Caches the XSLT stream. /// </summary> private Stream xsltStream; /// <summary> /// Caches the XSLT Arguments stream. /// </summary> private Stream xsltArguments; /// <summary> /// Cache the XslTransform. /// </summary> private XslTransform xslTransform; /// <summary> /// Caches the XSltArgumentList. /// </summary> private XsltArgumentList xslArgumentList; /// <summary> /// Constructor. /// </summary> /// <param name="XsltStream">Stream of XSLT as XML.</param> /// <param name="XsltArguments">Stream of Extension Objects as XML.</param> public BizTalkMap(Stream XsltStream, Stream XsltArguments) { xsltStream = XsltStream; xsltArguments = XsltArguments; } /// <summary> /// Transforms the given instance and returns the result as a stream. /// </summary> /// <param name="inXml">Stream of the instance to transform (XML)</param> /// <returns>Stream of the transformed XML.</returns> public Stream TransformInstance(Stream inXml) { XslTransform transform = Transform; XmlDocument xmlInputDoc = new XmlDocument(); // Make sure we do not destroy the formatting xmlInputDoc.Load(inXml); // Output stream MemoryStream outStream = new MemoryStream(); XmlTextWriter xmlWriter = new XmlTextWriter(outStream, System.Text.Encoding.UTF8); // Formatting options xmlWriter.Formatting = Formatting.Indented; xmlWriter.Indentation = 2; // Perform transformation - We do not specify a resolver transform.Transform(xmlInputDoc, TransformArgs, xmlWriter, null); // Prepare the output stream outStream.Seek(0, SeekOrigin.Begin); return outStream; } /// <summary> /// Gets an instance of XslTransform for the given XSL/Extension objects. /// </summary> private XslTransform Transform { get { if (xslTransform == null) { // Create a new transform XmlTextReader xsltReader = new XmlTextReader(xsltStream); XslTransform transformTemp = new XslTransform(); transformTemp.Load(xsltReader, (XmlResolver) null, GetType().Assembly.Evidence); // Cache the transform xslTransform = transformTemp; } return xslTransform; } } /// <summary> /// Gets a XsltArgumentList from a BizTalk Extension Object XML. /// </summary> private XsltArgumentList TransformArgs { get { if (xslArgumentList == null) { XmlDocument xmlExtension = new XmlDocument(); XsltArgumentList xslArgList = new XsltArgumentList(); if (xsltArguments != null) { // Load the argument list and create all the needed instances xmlExtension.Load(xsltArguments); XmlNodeList xmlExtensionNodes = xmlExtension.SelectNodes("//ExtensionObjects/ExtensionObject"); foreach (XmlNode extObjNode in xmlExtensionNodes) { XmlAttributeCollection extAttributes = extObjNode.Attributes; XmlNode namespaceNode = extAttributes.GetNamedItem("Namespace"); XmlNode assemblyNode = extAttributes.GetNamedItem("AssemblyName"); XmlNode classNode = extAttributes.GetNamedItem("ClassName"); Assembly extAssembly = Assembly.Load(assemblyNode.Value); object extObj = extAssembly.CreateInstance(classNode.Value); xslArgList.AddExtensionObject(namespaceNode.Value, extObj); } } // Cache the list xslArgumentList = xslArgList; } return xslArgumentList; } } } }
FileStream fsXslt = null; FileStream fsInput = null; FileStream fsExtensions = null; FileStream outStream = null; try { // Transform fsXslt = new FileStream(xsltPath, FileMode.Open, FileAccess.Read); fsInput = new FileStream(instancePath, FileMode.Open, FileAccess.Read); fsExtensions = (extensionPath != null) && (extensionPath.Length > 0) ? new FileStream(extensionPath, FileMode.Open, FileAccess.Read) : null; BizTalkMap map = new BizTalkMap(fsXslt, fsExtensions); Stream sOut = map.TransformInstance(fsInput); // Save stream to a file string destPath = Path.Combine(Path.GetDirectoryName(instancePath), Path.GetFileName(instancePath) + ".trans.xml"); outStream = new FileStream(destPath, FileMode.Create, FileAccess.Write); outStream.Write(((MemoryStream) sOut).ToArray(), 0, (int) sOut.Length); } finally { if (fsXslt != null) fsXslt.Close(); if (fsInput != null) fsInput.Close(); if (fsExtensions != null) fsExtensions.Close(); if (outStream != null) outStream.Close(); }