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.

 

 

Sample Code

XSLT_Bad_Value-Of_element

Error

MyMap.btm: error btm1023: Exception Caught: The ‘=’ character, hexadecimal value 0x3D, cannot be included in a name. Line 4, position 23.

Solution

This was a simple typo. I typed in “value-of-select=” instead of “value-of select=” (I erroneously included an extra dash between the “of” and the “select”). The parser saw the entire element name as “value-of-select=”, so of course, you cannot have an equal sign in an element name.

Context

I was trying to merge data from two incoming messages in a BizTalk Map, using the XSLT technique described here:
https://blogs.msdn.microsoft.com/biztalkcpr/2009/06/01/how-to-join-two-schemas-in-a-map-when-they-contain-namespaces/

Two things that I would like to add to that blog:

  1. You need to use an XSLT Template instead of just inline XSLT because you need to pass a parameter.
  2. When using the XSLT Template, you need to connect the Scripting Functoid to a field on the right side of the map; otherwise the Template will never get called.

 

How do you test a multi-part map, i.e. a map with multiple input messages?

To test a normal map, you just take an sample XML file, click the map (in Visual Studio Solution Explorer), set the property of the filename into the value of the property “TestMap Input”, then right-click on the map and select “Test Map”. The output is shown in the Output window, and you can do a CNTL-Click on the output file to view the results.

Maps created in the normal mapper usually just have one input message; but maps created in an orchestration can easily have multiple input messages (just by selecting more than one incoming message in Transform shape). This creates a special structure, or wrapper around the messages. You can still do a test-map, but the file you input to the test map must be in the special format as follows:

<ns0:Root xmlns:ns0="http://schemas.microsoft.com/BizTalk/2003/aggschema">
	<InputMessagePart_0>
		<ns1:Schema1RootElement xmlns:ns1="http://MyNamespace.Schema1">
		</ns1:Schema1RootElement>
	</InputMessagePart_0>
	<InputMessagePart_1>
		<ns2:Schema2RootElement xmlns:ns2="http://MyNamespace.Schema2">
		</ns2:Schema2RootElement>
	</InputMessagePart_1>
</ns0:Root>

You would put your own elements and attribute between Schema1RootElement and it’s closing element, as well as between Schema1RootElement2 and it’s closing element.

So normally, you use some text or XML editor (like NotePad++) and paste data samples, then wrap them with the XML element wrappers shown above.

If your schemas don’t have target namespaces, you can simplify as follows:

<ns0:Root xmlns:ns0="http://schemas.microsoft.com/BizTalk/2003/aggschema">
	<InputMessagePart_0>
		<Schema1RootElement>
		</Schema1RootElement>
	</InputMessagePart_0>
	<InputMessagePart_1>
		<Schema2RootElement>
		</Schema2RootElement>
	</InputMessagePart_1>
</ns0:Root>

So basically the root element is part of a special namespace called the “aggschema” (aggregate schema). Then, as many message as you added each are found, but each is wrapped with N) where N start with 0, and increments once per each message.

I had a case today where I was mapping two fields N01 and N02 to two side by side fields in the output schema.

N01 was working, and N02 wasn’t.  Both were under an condition that a certain variable MESSAGE_TYPE was equal to a certain value.  Yet one worked, the other didn’t?   How did I go about debugging this?

I did a right click “Validate Map”, and opened the XSLT.  I searched for N01, and found the following.

XSLT_Debug_Sample_1

Can you spot any clues to the issue?  If not scroll on down…

 

 

 

 

 

 

 

 

 

 

 

 

 

In the image below, I have highlighted some of the code. The N01 is wrapped by one IF statement, so I would expect the same IF statement around N02.

But when I look at N02, I see to nested IF statements.

XSLT_Debug_Sample_2

You can find the variables to understand what the tests are.  So I searched for the extra test around N02, i.e. the variable v16, and found this.

XSLT_Debug_Sample_3

That jogged my memory.  I had another page on the map, where we were using a “trick” to not map certain fields.
This was a RosettaNet schema, and it contained many nodes with default values that were filling up the output document with unnecessary junk. The “trick” is to map “false” to the fields you don’t want to map.  So I had essentially told the map that I did not want to output this element (even though I had mapped something to it).

Of course, this is only one small case, but I hope you like the methodology, and can use it to apply to your own debugging.  Learning XSLT of course helps.  The XSLT generated by BizTalk can be a little confusing because of all the “userCSharp” routines, and all the variables, but it can help you find issues in your map while testing.

Also note, I would not have found this issue unless I was carefully unit-testing.  I hand-crafted 5 different input test files, and was running each one of them through the “Test Map” feature, and manually scanning the output.