Mapping With External Assemblies Part 1

Introduction

Part 1 - Inline scripting and external assemblies
Part 2 - XSLT Call Templates and custom extensions
Part 3 - Cascading Functoids

This is the first of three articles about BizTalk mapping with external assemblies. External assemblies or helper classes are used by BizTalk's mapping engine (XSLT) automatically, and they can also be used explicitly by the developer. Just as in an orchestration, a developer may find a requirement that the built in mapping tools ("functoids") do not address and in this case judicious use of a helper class can be very... helpful.

While these articles are not an introduction to mapping if you have a little experience with the BizTalk mapper and a little experience with XSLT you should be able to follow along.

Part 1, this article, introduces inline C# scripting, its limitations, and also introduces the scenario of using an external assembly (helper class) automatically. Part 2 demonstrates using a map's custom extension property, which allows us to explicitly access external assemblies. Part 3 will build on this discussion and highlight potential caveats about using cascading functoids; when the output of one functoid  is the input to another functoid(s).

Inline Scripting with C#

As you probably know, using C# within a map is very easy to do. Drag the scripting functoid to the map:


From Toolbox to Design Window   

Right click on the scripting functoid and add your C# code:


From Funtoid Menu to Script Dialog 

Tip: In a previously saved map, if you switch between Script Types be sure to click the Reset button first, otherwise old settings (including C# code!) will stick around and the Script Type property won't necessarily change even though there is no warning.

Tip: You can display friendly names for your input parameters if you set the Label property for each link.  View the link properties by clicking each link (line) in the map.

Unlabeled Links
Configure Functoid Inputs 1 

Label Property
Link Properties

Labeled Links
Configure Functoid Inputs 2

Limitations of Inline Scripting

Validate Map Once the map is completed we can validate it and view the generated XSL file. I encourage you to create a simple map, validate it, then look at the resulting XSL file... even if you know only a smattering of XSLT you can gain lots of insight into how the actual mapping is working (or not!).

If we look at the XSL file from the map above we discover that the BTS Mapper puts all of our inline C# code into a CDATA section. There is a screenshot below. Then within an XSLT statement our C# method is called. This is actually the key to the whole series of articles! In this case the BTS Mapper designed the XSLT extended syntax for us, but as you will find out in part 2 we can do it for ourselves!

So, why would we ever want to do manually what the BTS Mapper can do for us automatically? Well, if you reflect on the above paragraph you'll notice that our C# method is called from an XSLT statement. To highlight this point, below are the snippets from the XSL of our map:

This section is how the XSL inline C# is automatically defined within a CDATA section. Noticed the prefix "userCSharp" as in the future this will come in handy.

<
msxsl:script language="C#" implements-prefix="userCSharp"><![CDATA[
    public string MyInlineSortKey(string str1, string str2)
    {
        const string comma = ",";
        return String.Concat(str1, comma, str2);
    }
]]></msxsl:script>


Here is the actual XSLT that is generated. A variable named "v1" is created and our method is called by passing the XPATH values of LastName and FirstName. The actual use of the C# method is very straight forward.

<xsl:variable name="var:v1"
              select="userCSharp:MyInlineSortKey(string(Customer/LastName/text()),
                      string(Customer/FirstName/text()))
" />
<
SortKey>
    <
xsl:value-of select="$var:v1" />
</
SortKey>

To come back to the question, why would we want to implement a call to a helper class manually? It's because there are limitations to what you can do with inline C#. Specifically, inline C# that looks like the following will not compile even if the MyMapUtility reference was added to the map project.

MyMapUtility.Strings stringUtil;
return
stringUtil.MySortKey(str1, str2);

From MSDN (references at end):

BizTalk saves inline scripts in the Extensible Stylesheet Language Transformations (XSLT) stylesheet defining the map. Because of this, inline scripts may use the same namespaces as any other XSLT stylesheet script.

What this means is that even if you add the helper class reference to your map project, you will not be able to call your methods with inline scripting and the map project will not compile. Part 2 will cover how this can be accomplished using XSLT call templates. Here are the namespaces that are supported for inline scripting, so remember with inline C# scripting you can use these namespaces but no other:

Supported namespaces for inline scripting
Namespace Description
System The System class
System.Collection The collection classes
System.Text The text classes
System.Text.RegularExpressions The regular expression classes
System.Xml The core XML classes
System.Xml.Xsl The XSLT classes
System.Xml.Xpath The XPath classes
Microsoft.VisualBasic The Visual Basic script classes.

External Assembly Script Types

Script Type External AssemblyAs we've examined, inline C# scripting only supports a certain set of namespaces. And in part 2 we'll look at custom XSLT templates that let use use any external assembly. However, there is functionality out of the box that the BTS mapper provides for using External Assemblies.

The main caveat is that the default constructor (parameterless) on the external class will be used. In some cases you may find yourself wanting to create a wrapper class created especially for mapping that handles a custom initialization of the actual helper class.

Knowing how the External Assembly and method call is incorporated into the map will help us in part 2 when we describe manually using an external assembly.

Here is a example helper class that is designed to return two strings separated by a comma. After creating the helper class add its reference to the Map project.

Typical helper class. This method will take two inputs and return a value:

namespace RDA.BizTalk.Learning.MapUtility
{
    ///
<summary>
    ///
String Utilities
    ///
</summary>
    [Serializable]
    public class
Strings
    {
       
// A custom sort key
       
//
        public string MySortKey(string str1, string str2)
        {
            const string comma = ",";
            return String.Concat(str1, comma, str2);
        }
    }
}


Assign the External Assembly to your scriptoid and pick your class method:
image
TIP:
If you right click and "Test" a map that uses an external assembly, the assembly must already be added to the GAC, otherwise the test will fail.

Validate MapNow let's examine what the mapper does with the external assembly to call it. We can do this by right clicking the map and picking "Validate". The output of the validation process is the XSL file and an Extension Object XML file.

The Extension Object XML file was created automatically based on all the external assemblies used in the map (in this case, just one). It contains the assembly reference that the XSL will use. It looks like the following:

Extension Object XML:

<ExtensionObjects>
  <
ExtensionObject
      Namespace=
"http://schemas.microsoft.com/BizTalk/2003/ScriptNS0"
      AssemblyName="
RDA.BizTalk.Learning.MapUtility,
          Version=1.0.0.0,
          Culture=neutral,
          PublicKeyToken=f7eb52812c0b3fa1
"
      ClassName="RDA.BizTalk.Learning.MapUtility.Strings"
/>
</
ExtensionObjects>

Now, let's examine the XSL file. It turns out that the use of the external assembly will look almost exactly like how the inline C# code was called. The only difference is that in this case in the header of the XSL file a namespace alias is created for "ScriptNS0". I encourage you to validate your own simple map that uses an external assembly.

External Assembly XSLT:

<
xsl:variable name="var:v1"
             
select="
ScriptNS0:MySortKey(string(Customer/LastName/text()),
                      string(Customer/FirstName/text()))
"
/>
<
SortKey
>
    <
xsl:value-of select="$var:v1"
/>
</
SortKey
>


The inline C# code XST from the previous section for comparison:

<
xsl:variable name="var:v1"
              select="
userCSharp:MyInlineSortKey(string(Customer/LastName/text()),
                      string(Customer/FirstName/text()))
"
/>
<
SortKey
>
    <
xsl:value-of select="$var:v1"
/>
</
SortKey
>

TIP: The "userCSharp" prefix is used for all of your inline methods. If you have your own inline XSLT transformations you can call previously defined inline C# methods yourself!

TIP: Though not necessarily a best practice it is possible to create a "floating" scriptoid (not connected to anything!) that just contains a few methods (yes, more than one) that you will call elsewhere in the map. You can call them from custom XSLT, and you can also call them from inline C#! In this manner you could have a GET and SET method to cache calculated values that are valid during the execution of the map.

TIP: A scriptoid must have a least one output to the target schema in order to be executed by the XSLT map. Otherwise, the map will still compile however the scriptoid will not execute.

External Links from MSDN


External Links about BizTalk Mapper Performance

No comments: