Between many artifacts BizTalk Server provided maps. Maps allow XML Source messages transformation into XML Destination messages. For that purpose, there are basically three approaches.
The BizTalk Mapper is an integrated tool of Visual Studio that allows you to graphically specify both its input and output XSD schemas. Once the operation is fulfilled, simple affectations are straightforward. For more complex operations, BizTalk brings to us a functoïds Toolbox. These elements achieve various treatments (string operations, logic, database access, etc.).
Nonetheless, we quickly realize that some transformations still too laborious or impossible to implement with the provided functoïds.
For these cases, we can use the scripting functoïd. It allows inline C#, Jscript, VB or XSLT scripts integration. Moreover, it is possible to run external methods in your transformation.
This functoïd helps you to perform every kind of transformations, even complex ones. However, for some processing this may quickly become difficult to read and therefore to maintain.
To avoid this drawback, the alternative is to build our transformation directly into XSLT. This option is less graphical. Some will say that it is also harder to maintain. The fact is that this often allows to carry out complex treatments relatively quickly. We will detail this approach here.
We’re going to set up a XSLT solution to meet the following problem : from a raw list of persons in input of the map, we want to get a list of unique persons (with a full name in capital letters)
For this purpose, it is necessary to create an empty map (.btm) and specify the source and destination schema, then click onto the grid to access to the right properties :
The « Custom XSLT Path » property gives the path to a XSL file describing the transformation from the source message to the destination message.
By creating the file below (and adding its URI in the « Custom XSLT Path » property), it is possible to figure out our problem.
<?xml version="1.0" encoding="UTF-16"?>
<xsl:stylesheet xmlns:xsl="https://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:var="https://schemas.microsoft.com/BizTalk/2003/var" exclude-result-prefixes="msxsl var s0 userCSharp" version="1.0" xmlns:ns0="https://XSLTransform.DestinationSchema" xmlns:s0="https://XSLTransform.SourceSchema" xmlns:userCSharp="https://schemas.microsoft.com/BizTalk/2003/userCSharp">
<xsl:output omit-xml-declaration="yes" method="xml" version="1.0" />
<xsl:key name="uniqueName" match="Person" use="Name"/>
<xsl:template match="/">
<xsl:apply-templates select="/s0:Persons" />
</xsl:template>
<xsl:template match="/s0:Persons">
<ns0:Persons>
<xsl:for-each select="key('uniqueName', Person/Name)">
<xsl:if test="not(preceding-sibling::*[1]/Name = Name)">
<xsl:variable name="fullUpperName" select="userCSharp:GetUpperCaseFullName(Name/text(), FirstName/text())"/>
<Person>
<FullName>
<xsl:value-of select="$fullUpperName" />
</FullName>
</Person>
</xsl:if>
</xsl:for-each>
</ns0:Persons>
</xsl:template>
<msxsl:script language="C#" implements-prefix="userCSharp"><![CDATA[
public string GetUpperCaseFullName(string pName, string pFirstName)
{
string fullName = pName + " " + pFirstName;
return fullName.ToUpper();
}
]]>
</msxsl:script>
</xsl:stylesheet>
This code performs a transformation which would be impossible to set up with simple functoïds, in the mapper. In addition, we can see that this is obviously simpler than using an external method, through a scripting functoïd.
We also remind that BizTalk does not handle XSLT version up to 1.0. However, as a workaround, it is possible to use C# scripts inside the « Script » tag and then calling them, in the XSL, prefixed by the value of the « Implements-Prefix » attribute.
If the from scratch creation of the XSL file seems too complex or if the map already exists, we can generate this file. You have to right click on the map and then click « Validate Map ».
In addition, external method (from another assembly) can be call into the XSL file, after referencing the assembly. This requires to first create an extension file (.xml) containing the strong name of each external assembly:
<ExtensionObjects>
<ExtensionObject Namespace="https://schemas.microsoft.com/BizTalk/2003/ScriptNS0" AssemblyName="XSLTransform.Helper, Version=1.0.0.0, Culture=neutral, PublicKeyToken=dbf4b132923889f0" ClassName="XSLTransform.Helper" />
</ExtensionObjects>
The URI of this file must be specified in the « Custom XML Extension » property of the map. It is then possible to modify the XSL file as follow :
<?xml version="1.0" encoding="UTF-16"?>
<xsl:stylesheet xmlns:xsl="https://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:var="https://schemas.microsoft.com/BizTalk/2003/var" exclude-result-prefixes="msxsl var s0 userCSharp ScriptNS0" version="1.0" xmlns:ns0="https://XSLTransform.DestinationSchema" xmlns:s0="https://XSLTransform.SourceSchema" xmlns:userCSharp="https://schemas.microsoft.com/BizTalk/2003/userCSharp" xmlns:ScriptNS0="https://schemas.microsoft.com/BizTalk/2003/ScriptNS0">
<xsl:output omit-xml-declaration="yes" method="xml" version="1.0" />
<xsl:key name="uniqueName" match="Person" use="Name"/>
<xsl:template match="/">
<xsl:apply-templates select="/s0:Persons" />
</xsl:template>
<xsl:template match="/s0:Persons">
<ns0:Persons>
<xsl:for-each select="key('uniqueName', Person/Name)">
<xsl:if test="not(preceding-sibling::*[1]/Name = Name)">
<xsl:variable name="fullUpperName" select="concat(ScriptNS0:ToUpper(Name/text()), ' ', ScriptNS0:ToUpper(FirstName/text()))"/>
<Person>
<FullName>
<xsl:value-of select="$fullUpperName" />
</FullName>
</Person>
</xsl:if>
</xsl:for-each>
</ns0:Persons>
</xsl:template>
</xsl:stylesheet>