Here’s an example where I wanted to create a small XML file with a unique list of TerminalIDs from my application.

On the left, which I had to blur out, the Looping functoid is connected to a parent record that repeats, and the other lines are connect to the source of the TerminalID.

The scripting component has the following “Inline C#” code:

public System.Collections.Generic.List<string> duplicateList = new System.Collections.Generic.List<string>();

public bool IsDuplicate( string terminalID )
{
     if( duplicateList.Contains( terminalID) )
        return true;
     duplicateList.Add( terminalID );
     return false;
}

You can see that it creates a collection object called a "Generic.List". It then adds terminalIDs to that list, after checking if the item is already in the list or not. It then returns a true or false.

The logical equals functoid tests the output of the above to "false". I think it is used to make sure we send a boolean true/false to the item on the right side of the map. Maybe it could be avoided, but I saw this in someone else's map, and I left it for now. I made one attempt to remove it and reverse the true/false coming out of Scripting Component, but it didn't work.

In case you didn't know, when set a field on the right side of the map to "false", then that item doesn't get mapped out. You basically suppress it.

The output, when I passed two inputs with "ABC" as the terminal ID is as follows:

<?xml version="1.0"?>
<ns0:UtilListTerminalIDs xmlns:ns0="http://TMW.Integration.Schemas.UtilListTerminalIDs">
  <UtilListTerminalID>
     <TerminalID>ABC</TerminalID>
  </UtilListTerminalID>
</ns0:UtilListTerminalIDs>

Remember that you can right-click the map, and click "Validate" to see the XSLT behind the map. The XSLT file will be shown in the Output window, and you can do a CNTL-Click on the .XSL filename.

Later, I passed two messages to the following C# routine which identifies if a TerminalID was not found in the target database (after doing a query on it). I could have also done the dup logic in the C# routine. This code uses XPath and the XmlDoc.SelectingSingleNode method to lookup the terminal ID in the results of the query. (The database query was previously returned as an XML message in the orchestration.)

I didn't want the orchestration to continue, because it would get an error later when it tries to insert a row into another table that requires the TerminalID to be in the target database.

        public static string FindMissingTerminalIDsInTRMSTResponse(
XmlDocument UtilListTerminalIDs, XmlDocument TRMSResponse)
{
string rt = "";
string xpathTerminalIDs = "//TerminalID";
XmlNodeList terminalNodes = UtilListTerminalIDs.SelectNodes(xpathTerminalIDs);
int countMatches = 0;
int countNodes = 0;
int countMisMatches = 0;

foreach (XmlNode terminalNode in terminalNodes)
{
countNodes++;
string xpathVerify = "//*[local-name()='TSATRM'][text()='" + terminalNode.InnerText + "']";
Console.WriteLine("xpathVerify=" + xpathVerify);

XmlNode testTerminalExistsNode = TRMSResponse.SelectSingleNode(xpathVerify);

if (testTerminalExistsNode != null)
{
countMatches++;
}
else
{
countMisMatches++;
if (countMisMatches > 1)
{
rt = rt + ", "; // comma separate after the first one
}
rt = rt + terminalNode.InnerText;
}

}

At first, I was going to return a boolean from this method, but then I decided that I would suspend the orchestration if the input had missing TerminalIDs, and the message should list all the missing IDs in a string. That went into my orchestration Suspend Shape.

I’ve done this many times before, but today, I had numerous issues.

1. I didn’t give the C# subroutine a strong name, and thus it wasn’t in the GAC. This result in “Load Failure” or “Could not load file or assembly…”

2. GacUtil getting code 1 or code 3. I think the code 1 was that I was using a different release of .NET, but not sure. Then the code 3 was because I didn’t have a strong name on it. Obviously, you cannot GAC without a strong name. (I also run a PowerShell to do a GAC, but it gives no errors at all; I’m going to start using GACUTIL instead of that; because at least it gives error when it needs to. The PowerShell fails silently, and acts like it worked.

3. I wrote a C# console tester, then discovered that I forgot to make the methods static; did that, tested in Console, then did a GACUTIL again.

4. Orchestration was still failing. I tried resume, but ended up having to do a full deploy, restart hosts, and resubmit data. I think the fact that whether the routines were static or not was somehow embedded in the map in the same project in which the orchestration was found.

In a BizTalk map, you can add scripting functoids without connecting them to anything. They can contain functions, as shown below.

Note 1: You must add a constant for each variable, so if you have 3 parameters, then add three constants values on the “Functoid Inputs” tab.

Note 2: They will have a warning icon on them, indicating no inputs or outputs.

Now, anywhere else in the map, you can add a Scripting Functoid, and simply call the shared C# function from above:

A better approach is probably have to have a C# Map.Helpers class/assembly where you have all the reusable functions. Then you can also re-use them across different maps.

Background

BizTalk let’s you create a map that uses XSLT instead of the functoids/GUI mapping. To do this, you click on the mapping grid, go to the “Grid Properties” window, and put in a filename for the “Custom XSLT Path”. I usually name it the same as the map file, just substituting the .xslt for the .btm file suffix.

In your XSLT file

Take some typical functoid map that uses a C# external library. If you look at the generated XSLT (right click the map and select “Validate”, then open the XSLT file_), you will see something like this:

xmlns:ScriptNS0="http://schemas.microsoft.com/BizTalk/2003/ScriptNS0"


Actually there will be one of these for each class that you reference.

The actual call to the c# may look like this:

   <xsl:value-of select="ScriptNS0:XMLDateToTimeHHMMSS($parmDateTime)"

In a normal map, it may be hard to figure out which ScriptNS# correlates to which class/library/.DLL. (Of course you have to reference that .DLL in your References for that project.)

Create your own Custom Extension File

This is where the magic happens! This file ties together your .DLL/classname to the ScriptNS# namespace to be used in your custom XSLT code.

Note: You don’t have to use ScriptNS0. IN the example below, I used ScriptDateFunctions, since the class name is DateFunctions. It doesn’t even have to have the word “Script” in it, but I leave that there to make sure it’s more clear.

<ExtensionObjects>  
   <ExtensionObject  
      Namespace="http://schemas.microsoft.com/BizTalk/2003/ScriptDateFunctions"  
      AssemblyName="ABC.Common.Helpers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a6349ee3fd01c3ec"  
      ClassName="ABC.Common.Helpers.DateFunctions" />  
</ExtensionObjects>  

You can get the AssemblyName, Version, Cultural and Public Key Token from the GAC (Global Assembly Cache). Navigate to your the folder:
c:\windows\microsoft.net\assembly\GAC_MSIL\ABC.Common.Helpers\

Repeat the “ExtensionObject” for each className. Note that if your .DLL has 4 classes, then you would need 4 ExtensionObjects (if you want to call all methods in all 4 classes).

I usually save this as a file called CustomExtension.xml. You could have one for each map, or one shared across all your maps.

Setup the Custom Extension for your Map

Just as you set the “Custom XSLT Path” in a previous step, click on the map grid, go to the “Properties” window, and paste or select your filename for “Custom Extension XML”.

Back to your XSLT

In the xsl:stylesheet root element, make sure you add your namespace:

xmlns:ScriptDateFunctions="http://schemas.microsoft.com/BizTalk/2003/ScriptDataFunctions"

Make sure the namespace corresponds to the class that contains the method you will call. For example, my “DateFunctions” class has a method called “XMLDateToTimeHHMMSS”.

   <xsl:value-of select="ScriptDateFunctions:XMLDateToTimeHHMMSS($parmDateTime)"

Test Your Map

Provide a sample instance to test the map. (Click on the .btm file in Solution Explorer), then in the “Properties” window, paste in the name of a file for the “TestMap Input Instance”. Now right click the .btm filename in Solution Explorer, and click “Test Map”. As usual, check for any errors in the “Output” window, and CNTL-Click on the file at the bottom where it says “Output is stored in the following file…”

Possible Errors

Prefix ‘ScriptDateFunctions’ is not defined.
You didn’t include the namespace at the top of your XSLT file.

Summary: We have now used the BizTalk “Custom Extension XML” to allow you to make calls to C# from your custom XSLT maps.

I got this error from the following Scripting Functoid with inline C# script:

public int PutHL03()
{
     if (hl01 > 2) return "I"; 
     else if (hl01 == 1) return "S"; 
     return "0"; 
}
You can see it is returning string, but I specified the return type as “int”. Just changed “int” to “string” as the return type, and it worked. At least this error, pointed to “Inline Script”. See this StackOverflow question/answer I posted with a similar error, but there the I could have sworn the error was related to an XSLT sum, when it was really just a small error in a similar scripting functoid. https://stackoverflow.com/questions/55070442/biztalk-map-xslt-1-0-sum-cannot-implicitly-convert-type-string-to-int

Error when running from BizTalk run time environment: Exception from HRESULT: 0x80131942
(no other details were provided)

Exact error when running “Test Map” in Visual Studio: Does not contain a matching ‘GetTimestamp’ method that has 0 parameter(s).

I was transferring a BizTalk map from 2010 to 2013.

There was an XSLT inline that had the following:

<xsl:variable name="timeStamp" select="userCSharp:GetTimestamp()"/>

and had this C# function:

<ScripterCode><Script Language="CSharp">
<![CDATA[public string GetTimeStamp()
{
DateTime date = DateTime.Now;
return date.ToString("HH:mm");
}]]>
</Script>
</ScripterCode>

It turns out in my particular case, the function name didn’t actually match, the “s” of “GetTimeStamp” either needed to be capitalized or lower case in both places where it is used.

This blog also shows how to get the time within an XSLT function (in a BizTalk map).

When doing a test map, you could get dozens or hundreds of the following error:

error btm1044: Input validation error: Could not find schema information for the element ‘http://Namespace/:ElementName.

Visual Studio Solution Explorer allows you to right-click on a .btm map and specify a property called “TestMap Input Instance”. If you specify a file that totally doesn’t match the expected schema, then you can receive the error above.

How do you find the hidden map name in the BiztalkMgmtDB (BizTalk Database)?

If you look at the table names, you will obviously find bt_MapSpec, but it doesn’t contain the map name. The map itself is hidden in the bts_Item table, which you have to join to. I found this on Jeroen Maes Integration Blog. He has a more complex query that finds a map based on the input/output target namespace. He also joins to the bts_Assembly table.

My goal was just to list all maps containing some sequence of letters (such as a customer name or abbreviation).


use BizTalkMgmtDb
select i.name, * from bt_MapSpec m
inner join bts_item i on m.itemid = i.id
where i.Name like '%ABC%' -- just the map name
-- where i.FullName like '%ABC%" -- optionally use the fully qualified name

I’m surprised there the Type column doesn’t seem to be populated with some number that indicates that the bts_Item is a map, or a schema, or whatever.

Issue/Scenario:

I have a new application that I’m building. I have for example three projects, App.Common, App.In, and App.Out.

We have a somewhat debatable policy of keeping one app separate from the others and almost no application cross-references (still on BizTalk 2010 – but in 2013 this is supposed alleviated, partially or in whole).

I had a web service schema copied in to App.Out. And I developed the proof of concept in App.Out.

Now, it turns out we have about 6 output messages.  There was a also some debate as to whether they should go in the single AppOut project, or in six different projects (App.Out1, App.Out2, etc…).  The architect wanted the second approach, so different developers could be assigned to each one in order to possible meet a tight development deadline.

Now, I don’t want that same web service schema to be deploy 6 times in the same application. So I moved the schemas to the App.Common.  And of course the map doesn’t compile because I change the location of the schemas.

Solution

I was afraid I was going to have to do map surgery, but it turns out I could just re-reference the schema in the map.  Since the schema and nodes are the same, none of the mapping was lost.

Internals of the Map

I was afraid I was going to have to do map surgery, but it turns out I could just re-reference the schema in the map.  Since the schema and nodes are the same, none of the mapping was lost.

But for fun, if I had to do map surgery, this is chat change. The Original “Before” is on top, and the “After” is on the bottom”.

NOTE: When you open a map there are no line breaks.  I use the XML tools in NotePad++ to format the XML (but don’t save it after formatting, always do the formatting on a copy).

You can see below how the “Reference Location” attribute changed.
I’m mapping from our Canonical schema to an internal web service schema.

 

<SrcTree RootNode_Name="Canonical_DATA">
<Reference Location="CanonicalArtifacts.Schemas.MY_CANONICAL_SCHEMA_V4" />
</SrcTree>
<TrgTree RootNode_Name="OrderUpdateHeaderByMessageID_New">
<Reference Location="SchemasWebSvc\B2BData_tempuri_org.xsd" />
</TrgTree>

<SrcTree RootNode_Name="Canonical_DATA">
<Reference Location="CanonicalArtifacts.Schemas.MY_CANONICAL_SCHEMA_V4" />
</SrcTree>
<TrgTree RootNode_Name="OrderUpdateHeaderByMessageID_New">
<Reference Location="AppOut.SchemasWebSvc.B2BData_tempuri_org" />
</TrgTree>

 

I was getting this strange error yesterday:

Error

map_X_to_Y.btm: error btm1023: Exception Caught: Object reference not set to an instance of an object.

Solution

What I had done was copied a map from one project to another.  I had either not copied the schema, or I had copied the map to a different folder where it couldn’t find one of the referenced schemas.   So either I moved the map or added the missing schema, and all was well.

At first I was focusing on Functoids and possible logic errors, but then I expanded the schema(s) on the left, and noticed the following:

Map_Missing_Schema

You can see in the picture above that InputMessagePart_0 was missing the schema.