BizTalk Mapper Patterns: Calling an external assembly from Custom XSLT in BizTalk Server 2010

Posted: July 29, 2012  |  Categories: BizTalk Maps

This topic is not new in my blog, I’ve already talked about this in the past: Calling an external assembly from Custom XSLT – Custom Extension XML (Grid Property) however, I decided to revisit this functionality within BizTalk Server 2010 and you will know why soon.

So is usual in complex maps to have scripting functoid with custom inline XSLT, and sometimes is useful to call custom .Net components directly from XSLT.

Create a .Net Component

To illustrate this functionality, I decided to create a Class Library project: MapperExtensionsFunctions with a simple class where it is implemented as a method to format the numbers in three decimal places:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;

namespace MapperExtensionsFunctions
{
    public class MappingFunctions
    {
        public MappingFunctions()
		{
		}

        public string ToDecimalPoint(string Input)
        {
            CultureInfo ciEN = new CultureInfo("en-US", false);

            double ConvertionDouble = double.Parse(Input, ciEN);
            string Output = string.Format("{0:0.000}", ConvertionDouble);
            return Output;
        }
    }
}

How can we extend BizTalk map to support this functionality?

You can add a custom extension XML file to your solution in order to declare the namespace and use a method from a .Net assembly from XSLT.

The extension file should look something like this:

<ExtensionObjects>
  <ExtensionObject Namespace="http://schemas.microsoft.com/BizTalk/2003/ScriptNS0" AssemblyName="MapperExtensionsFunctions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cdbffba4cc751306" ClassName="MapperExtensionsFunctions.MappingFunctions" />
</ExtensionObjects>

Note: “http://schemas.microsoft.com/BizTalk/2003/ScriptNS0” is the “default namespace”, however, you can change it (see more here)

In the properties of your BizTalk Mapper, use the Custom Extension XML property to open the Select Custom Extension XML File dialog box, in which you can select the file that contains the custom extension XML for the map (file above).

Custom Extension XML

Finally, in your Inline XSLT functoid, you can use the methods from the assembly by:

<xsl:variable name="result" xmlns:ScriptNS0="http://schemas.microsoft.com/BizTalk/2003/ScriptNS0" select="ScriptNS0:ToDecimalPoint(MarketPrice/text())" />

Or in this case, inside Inline XSLT Call Template:

<xsl:template name="PriceTemplate">
  <xsl:param name="market" />
  <xsl:param name="owner" />
  <xsl:element name="Price">
    <xsl:variable name="pmarket" xmlns:ScriptNS0="http://schemas.microsoft.com/BizTalk/2003/ScriptNS0" select="ScriptNS0:ToDecimalPoint($market)" />
    <xsl:variable name="powner" xmlns:ScriptNS0="http://schemas.microsoft.com/BizTalk/2003/ScriptNS0" select="ScriptNS0:ToDecimalPoint($owner)" />
    <PriceMarket>
      <xsl:value-of select="$pmarket" />
    </PriceMarket>
    <PriceOwner>
      <xsl:value-of select="$powner" />
    </PriceOwner>
  </xsl:element>
</xsl:template>

Houston, we have a problem!

After Build, deploy and configure my project with success I decided to test my solution, but I keep getting the following error:

The Messaging Engine failed while executing the inbound map for the message coming from source URL:”C:\TEMP\PORTS\IN_CAR\*.xml” with the Message Type “http://BizTalk.CallingExternalAssemblyFromInlineXSLT.CarInfo#CarProperty”. Details:”Cannot find the script or external object that implements prefix ‘ScriptNS0’.”

At first glance, this error suggests that the assembly in question is not published in the GAC… However even after re-publish, the assembly in the GAC the problem remained!

Don’t panic, believe it or not, you did everything right… BizTalk Server 2010/Visual Studio 2010 has a bug (or issue): Visual Studio does not persist the path of Custom Extension XML property in the .BTM file.

So if someone wishes to use an external assembly in via an Inline XSLT/XSLT Template scripting functoid they cannot specify the external assembly through an extension XML file.

I tried to install the latest cumulative update package for BizTalk (CU5) and Visual Studio service pack but the issue still remains active.

Workaround (unfortunately it’s manual)

You need to manually edit the .BTM file to add this node between the elements </ScriptTypePrecedence> and <TreeValues>:

<CustomXSLT XsltPath="" ExtObjXmlPath=".\ExternalAssembly.xml" />

Note: Is not mandatory the CustomXSLT node stand between the elements </ScriptTypePrecedence> and <TreeValues>, however, this is normal behavior of the compiler

However, this is a workaround that can cause many problems, especially maintaining this issue… but stay tuned, because in my next post I will explain how we can solve this problem in a better way.

You can download the source code from:
Calling an external assembly from Custom XSLT in BizTalk Server MapsCalling an external assembly from Custom XSLT in BizTalk Server Maps Tool
GitHub

Author: Sandro Pereira

Sandro Pereira lives in Portugal and works as a consultant at DevScope. In the past years, he has been working on implementing Integration scenarios both on-premises and cloud for various clients, each with different scenarios from a technical point of view, size, and criticality, using Microsoft Azure, Microsoft BizTalk Server and different technologies like AS2, EDI, RosettaNet, SAP, TIBCO etc. He is a regular blogger, international speaker, and technical reviewer of several BizTalk books all focused on Integration. He is also the author of the book “BizTalk Mapping Patterns & Best Practices”. He has been awarded MVP since 2011 for his contributions to the integration community.

12 thoughts on “BizTalk Mapper Patterns: Calling an external assembly from Custom XSLT in BizTalk Server 2010”

  1. Hi,

    I have a question about the version that should be specified in the custom extension xml file.

    In a context of several assembly versions deployed side by side, I have my extension file referencing the 1.0.0.0 version of a dll, whereas the BizTalk Project containing the map has a dll reference to the 1.0.0.1 version.

    This is causing an “object reference not set to an instance of an object.” error during map execution at runtime.
    I changed the version in the file to 1.0.0.1 and it worked (no new methods or else added in the last version!)

    Does the custom extension points to the project references or directly to the GAC ?

    That’s weird because I don’t have the error according the used environment.

    Thanks!

    1. That’s an excellent question… I didn’t try this scenario but for me it makes sense that the custom extension points directly to the file that is deploy in GAC.

      But again I never try this scenario before so I need to test this behavior to have sure.

  2. You should use double.TryParse and check the boolean result of that before returning a value. double.Parse will throw an exception if the string isn’t a valid double.

  3. I have been tasked with extending an existing Biztalk 2010 project. I need to pass a variable from a web request into a map within Biztalk . I have little experience with Biztalk and was wondering if this process of creating an instance of an object when I recieve the request and referencing it in the map is the best way to go.

    1. Hi John,
      For me the best way to accomplish this it really depends in the problem it self, but in general the the best way we will accomplished this is using a support schema and pass multiple shemas to the map by using the “Transform Shape” inside the orchestration (setting multiple input messages under “Source Transform”), i.e, you call the web service inside the orchestration and then send to input messages to the map. Check this source sample: http://code.msdn.microsoft.com/BizTalk-Mapper-Patterns-69cb788d

  4. Hi Sandro,

    Thank you for your quick and kind response. I shall attempt your suggestion of multiple schemas.
    The data received from the web request will be passed to an SAP database. The database will then send biztalk the response. It was decided that since the RFC in SAP is non-standard we can change this. So, we will pass the variable into the SAP RFC and simply return it without requiring SAP to do anything with it. Admittedly, the data will go to and from SAP, but it is a single value and will be returned in a table. I can then modify the mapping using the variable passed in. Seems much simpler.

    What do you think?

  5. Hi Sandro,

    Thank you for your response and sorry for my late post.

    So I just did the test and, you’re right, the extension object file directly references the GAC.
    In case of a missing dll version in the gac, a Test map raises the following message : “could not load file or assembly…”

    Other point, with a “standard” BTM map having a scripting functoid with a C# call, the extension object file is dynamically generated and takes the version referenced in the BizTalk project.
    (it can be seen with a validate map)

    But when this file is created by your own, the version has to be increased manually if necessary because the version referenced by the project does not matter.
    In the context of a BizTalk migration (DLL version increased and generated on a new environment), this manual update in each extension file is required.

  6. I have a different work around. Give your class a “HelloWorld” method. No inputs, just outputs a “Helllo, world” string. Add an external assembly functoid to your map calling this method. Connect that to a logical string functoid. Since HelloWorld outputs a string, the logical string functoid will always output “true”. Connect that to the root node of your destination message. This has no effect on your map output, but it does get the mapper too to include your class in its own generated extension objects. Now you can call it from your own custom XSLT without worrying about it being referenced properly.

Leave a Reply

Your email address will not be published. Required fields are marked *

turbo360

Back to Top