How do I reuse BizTalk 2004 maps outside of BizTalk?

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:

  1. Extract the XSL document produced by the mapper,
  2. If the map uses functoids (out of the box or custom functoids), you will need to extract the Xsl Transformation arguments,
  3. Create a .NET System.Xml.Xsl.XslTransfom() object,
  4. Create a .NET System.Xml.Xsl.XsltArgumentList() if there were any functoids in the map and instantiate appropriate objects,
  5. Call Transform() on the XSL and optionally, the XsltArgumentList.

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:

  1. If you have the map file (.btm) and can open it in Visual Studio 2003, you can right click on the map file in the solution explorer and select "Validate Map". The output window will give you the path(s) to the XSL and the extension Object XML. The links can be shift-clicked to retrieve the files,
  2. If you only have the compiled assembly, you can use the excellent Lutz Roeder's .NET Reflector to extract the required information as strings ,
  3. If you only have the compiled assembly, you can write some code that loads the assembly, creates an instance of the map object and calls the appropriate members. See the format of maps assemblies .

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="https://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;
         }
       }
   }
}

This class can be used as follows:

 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(); 
}

Comments

  • Anonymous
    September 21, 2004
    The comment has been removed

  • Anonymous
    September 22, 2004
    Nic: Glad you like it! Let me know if you encounter any problems with it.

  • Anonymous
    September 22, 2004
    Another cool post. Lately I've been using Reflector to look into the Microsoft.BizTalk.WebServices.ServerProxy, interesting stuff. It feels good to learn about the internal operation of BizTalk...

  • Anonymous
    October 10, 2004
    Hi,

    How can I download the full solution (zip file seems to be in a protected area)?

    Thanks
    ...colin

  • Anonymous
    October 11, 2004
    winisp is temporarily down and there is no estimated time of resolution. Retry in a few hours/days.

  • Anonymous
    July 31, 2005
    The comment has been removed

  • Anonymous
    September 03, 2005
    WSS vs Ent Lib : Here's something I found really interesting. In our dev workstations we hadnt installed...

  • Anonymous
    February 06, 2007
    PingBack from http://noise.youchill.com/?p=8

  • Anonymous
    February 15, 2007
    PingBack from http://blogs.rbaconsulting.com/banderson/PermaLink,guid,f388ded1-1fe9-44fe-9982-3d9991ceeb1f.aspx

  • Anonymous
    January 21, 2009
    PingBack from http://www.keyongtech.com/322199-invoking-a-map-dynamically