When developping complex maps with the BizTalk Mapper, you sometimes find yourself in a situation where you need to debug the underlying logic of the associated XML stylesheet.
In situations like these, it is customary to have Visual Studio generate an XSLT file from the BizTalk map, and use Visual Studio’s builtin support for debugging XSLT. This allows you to put breakpoints, inspect the content of variables and precisely monitor the transformation step by step.
Custom Extension Objects
There are two cases, however, that prevent you from debugging the underlying XSLT from within Visual Studio :
The first case involves the use of the various database functoids, for instance the cross-reference GetCommonValue, GetCommonID, GetApplicationValue and GetApplicationID functoids. These, are implemented in terms of external functions, in a separate assembly, that is itself referenced from the generated map.
The second case involves the use of the Scripting functoid to call an external function. This is a variation of the first case in which you specify the function you want to call.
In order to execute such maps, the BizTalk Mapper uses the XslTransform class by supplying a list of all the classes whose methods are required to carry out the XSLT transformation, along with their associated namespace prefixes.
The set of such custom classes used to provide external logic to XLST transformations is referred to as Custom XSLT Extension Objects. At design-time, these Custom Extension Objects are represented as an additional file that contains the associations between the declared XML namespace prefixes and the fully qualified .Net type names of the classes which contain the methods called from the map.
When you invoke the “Validate Map” context-menu option on a BizTalk Map in Visual Studio, you will notice that BizTalk generates this file, with a _extxml.xml suffix along with the associated XSLT.
Most of the time, this file is empty, for basic maps. This is not the case, however, if the map makes use of external functions in separate assemblies, as alluded to in the two cases described above.
Debugging XSLT with Custom Extension Objects
Unfortunately, such maps seemingly cannot be debugged inside Visual Studio without modifications. If you use Visual Studio’s builtin XSLT debugger and attempt to step into the underlying XSLT file, you will encounter an error such as this one:
---------------------
Cannot find the script or external object that implements prefix
'http://schemas.microsoft.com/BizTalk/2003/ScriptNS0'. "
Even though Visual Studio generates both an XSLT file and a _EXTXML.XML file, it seems there is no way to instruct Visual Studio about the custom extension objects file, which makes the debugging experience cumbersome and very frustrating.
Fortunately, there is a way!
Although there are no options in the Visual Studio GUI, all the support is there in the .Net Framework. All there is do is write a little C# program to do the trick.
A Simple Function to Debug Custom XLST Maps
The following function is very simple. In real life, this function would be compiled into a C# program with additional logic to accept and parse command-line arguments, in order to be able to supply paths to the stylesheet, the extension objects file and the input and output documents. This is no rocket science.
Here is the code:
using System;
using System.Collections;
using System.IO;
using System.Reflection;
using System.Text;
using System.Xml;
using System.Xml.Xsl;
class DebugHelper
{
static void Main(string[] args)
{
string input = @"...\input.xml";
string output = Path.ChangeExtension(input, ".out");
string stylesheet = @"...\stylesheet.xslt";
string extension_object = @"...\extension_extxml.xml";
string result = TransformXslt(input, stylesheet, extension_object);
}
private static string TransformXslt(string document, string stylesheet, string extension)
{
return TransformXslt(document, stylesheet, ParseExtension(extension));
}
private static string TransformXslt(string document, string stylesheet, object[] extension)
{
XslCompiledTransform transform = new XslCompiledTransform(true);
transform.Load(stylesheet, new XsltSettings(true, true), null);
XsltArgumentList arguments = new XsltArgumentList();
for (int index = 0; index < extension.Length; index += 2)
{
arguments.AddExtensionObject(
extension[index] as string,
extension[index + 1]
);
}
StringBuilder output = new StringBuilder();
using (XmlWriter writer = XmlWriter.Create(output, transform.OutputSettings))
transform.Transform(document, arguments, writer);
return output.ToString();
}
private static object[] ParseExtension(string extension)
{
if (String.IsNullOrEmpty(extension))
return new object[]{};
XmlDocument document = new XmlDocument();
document.Load(extension);
ArrayList extensions = new ArrayList();
foreach (XmlNode node in document.SelectNodes("/ExtensionObjects/ExtensionObject"))
{
string extension_namespace = node.Attributes["Namespace"].Value;
string extension_assembly = node.Attributes["AssemblyName"].Value;
string extension_class = node.Attributes["ClassName"].Value;
string assembly_qualified_name = String.Format("{0}, {1}"
, extension_class
, extension_assembly
);
object extension_object = Activator.CreateInstance(Type.GetType(assembly_qualified_name));
extensions.Add(extension_namespace);
extensions.Add(extension_object);
}
return extensions.ToArray();
}
}
A couple of points of interest need clarification.
First, the most crucial step, is to use the XslCompiledTransform constructor with a first parameter that indicates whether to execute the map in a debugger. This is actually what does the trick in this program.
When you run the program, it will launch the debugger and break at the beginning of the map. You can add this project to your Visual Studio solution, and you will be able to step into the map directly from the same Visual Studio instance.
Neat.
Secondly, what distinguishes this program from Visual Studio’s builtin XSLT debugger is the ability to actually make use of Custom XSLT Extension Objects.
The ParseExtensions function has been designed to consume an XML file, whose path is specified in the first argument, that has the same format as the ones generated by Visual Studio when using the “Validate Map” option.