Un des artefacts fourni par BizTalk Server est la map. Cette map permet de transformer un message XML Source en message XML Destination. Pour cela, il existe concrètement 3 approches.
Le mapper BizTalk est un outil intégré à Visual Studio permettant graphiquement de spécifier son schéma XSD en entrée et son schéma XSD en sortie de la transformation. Une fois que l’opération est réalisée, il est très rapide de décrire des transformations simples comme l’écriture d’un champ Source vers un champ Destination. Pour des choses plus complexes, BizTalk met à disposition une ToolBox remplie de fonctoïds. Ces éléments permettent de réaliser divers traitements (opérations sur les chaînes de caractères, logique, accès BDD, etc.).
Néanmoins, on se rend rapidement compte que certaines transformations deviennent trop laborieuses, voire impossibles, à réaliser avec les fonctoïds fournis.
Pour ces cas, BizTalk propose un fonctoïd Scripting. Ce dernier permet d’intégrer des scripts Inline (C#, JScript, VB, XSLT) ou même des méthodes d’assemblies externes à votre transformation.
Cette approche permet, techniquement, de réaliser l’ensemble des transformations, même complexes. Cependant, pour certains traitements, cela peut rapidement devenir indigeste, difficile à lire et donc à maintenir.
Pour éviter cela, il est possible de ne pas utiliser de fonctoïd et de construire sa transformation directement en XSLT. Cette option est moins graphique. Certains diront qu’elle est également moins facile à maintenir tout au long du projet. Le fait est que cela permet souvent de réaliser des traitements complexes relativement rapidement. C’est cette approche que nous allons détailler ici.
Nous allons mettre en place une solution XSLT afin d’adresser le problème suivant :
à partir d’une liste brute de personnes en entrée de la map, nous souhaitons obtenir une liste de personnes uniques (avec un nom complet en lettres majuscules).
Pour cela, il faut créer une map (.btm) vide en spécifiant le schéma Source et le schéma Destination. Ensuite, en cliquant sur la grille, on accède aux propriétés de la map :
La propriété « Custom XSLT Path » permet de donner le chemin vers un fichier .xsl qui décrira la transformation entre le message Source et le messageDestination.
En créant le fichier ci-dessous (et en ajoutant son URI dans la propriété« Custom XSLT Path »), il est possible de répondre à notre problématique.
<?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>
Ce code réalise une transformation qu’il serait impossible de mettre en place avec de simples fonctoïds, dans le mapper. En outre, on note que cela est visiblement plus simple que d’utiliser une méthode externe, au travers d’un fonctoïd Scripting.
Il faut également rappeler que BizTalk ne gère que la version 1.0 de XSL. Cependant, pour pallier à ce handicap, il est très facile de compenser en utilisant facilement des scripts C#, en les déclarant dans la balise « Script », puis en les appelant, dans le XSL, avec le préfixe mentionné dans l’attribut « implements-prefix ».
Si la création from scratch du fichier XSL semble trop complexe ou si, simplement, la map est déjà existante, avec un certain nombre de liens et/ou fonctoïds, il est possible de générer ce fichier. Pour cela, il faut cliquer droit sur la map en question puis cliquer « Validate Map ».
De plus, il est également possible, comme avec les fonctoïds Scripting, dans le mapper, de faire appel àune méthode externe (dans une autre assembly), après l’avoir référencée. Pour cela, il faut au préalable créer un fichier d’extension (.xml) contenant les informations de chaque assembly en question :
<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>
L’URI de ce fichier doit être renseignée dans la propriété « Custom Extension XML » de la map. Il est alors possible de modifier le fichier XSL comme suit :
<?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>