Table of Contents
1. Introduction
Within BizTalk Server development there exists a concept known as property promotion which involves defining an XML schema field (or attribute) to be either a Distinguished Field or a Property Field (or both). In both cases the promoted properties are added to a message’s context by the BizTalk messaging engine. However, there are a few important differences between the two types. Property fields can be used for message routing, tracking, are persisted to the BizTalk Message Box database and can be accessed throughout all the phases of BizTalk message processing. Distinguished Fields, on the other hand, can only be access in the BizTalk orchestration workflow, and are not used for routing or persisted; they are essentially aliases which reference an XPath expression pointing to the appropriate XML data field. The method of creating and using Distinguished Fields is as follows:
-
XML schema field(s) are promoted using the BizTalk schema editor as shown below:
-
The result of creating these promoted properties causes the BizTalk schema editor to add an xs:annotation field to the BizTalk schema with details of each of the promoted properties as shown below:
-
The result of creating these promoted properties causes the BizTalk schema editor to add an xs:annotation field to the BizTalk schema with details of each of the promoted properties as shown below:
<="https://social.technet.microsoft.com/wiki/resized-image.ashx/__size/550x0/__key/communityserver-wikis-components-files/00-00-00-00-05/8168.2.png" style="border-width:0px;border-style:solid;" />
-
Fields that are now marked as distinguished can be accessed in an orchestration workflow using the ‘dot notation’. This makes it easy to retrieve and assign values to the XML schema field, for example:
But when these BizTalk schemas are migrated to a Logic App integration account this functionality is lost; it is not available within the Logic App workflow designer. What follows in the next section of this article is a solution to this problem that will provide the developer with a similar method and development experience of accessing and using these schema fields.
The solution will make use of an Azure Logic Apps Custom Connector. The connector will have two actions: one for getting the schema distinguished field values and presenting them to the developer in the Logic App workflow at design-time, and one for setting the schema distinguished field values.
uAt design-time the connector ‘Get’ action will need to provide a drop-down list of Integration Account schemas to choose from as a parameter. It will also accept an XML message as a parameter to retrieve the actual value at run-time. It will also need to provide a list of Dynamic content swagger values in the designer representing the schema distinguished fields. The connector ‘Set’ action will also accept two parameters: an XML message and a list of schema distinguished fields and their new values as Key/Value pairs (the key being an xPath expression to the distinguished field).
The Custom Connector will need to make use the extended OpenAPI definitions for dynamic values and schemas, and the Azure Logic Management namespace.
Most of the functionality of this solution will be done within an Azure Function. The function will need to perform the following tasks:
-
Retrieve a list of XSD schemas stored in the Logic App Integration Account;
-
For a given XSD, return a JSON schema object representing the XSD distinguished fields;
-
For a given XSD and XML message, return a JSON object containing the distinguished field values;
-
For a given XML message and a set of Distinguished Field/Value pairs, return an updated XML message containing the new values.
Create a New Azure Function
The steps required to perform this are as follows:
-
Create a new Azure Function (C# HTTP trigger). A function like the one below should be created by the template:
- We now need to create a folder for the XSLT map. For this we need to use Kudu. The URL will be in the follow form (just replace the highlighted with the Function Apps name that is being used): https://xxdemos.scm.azurewebsites.net/DebugConsole/?shell=powershell
- Login to Kudu, select PowerShell from the Debug Console, navigate to the Azure Function Folder, and type mkdir Maps at the prompt to create a new sub-folder Maps, e.g.:
- Create a new file Promotions.xslt and upload the XSLT file to this new sub-folden>
- Promotions.xslt code is as follows:
<?xml version="1.0" encoding="UTF-16"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:var="http://schemas.microsoft.com/BizTalk/2003/var"
xmlns:s0="http://schemas.microsoft.com/BizTalk/2003"
xmlns:s1="http://www.w3.org/2001/XMLSchema"
xmlns:userCSharp="http://schemas.microsoft.com/BizTalk/2003/userCSharp"
exclude-result-prefixes="msxsl var s1 s0 userCSharp">
<xsl:output omit-xml-declaration="yes" media-type ="application/json" method="text" version="1.0" />
<xsl:param name="Output" select="="Output"'json'" />
<xsl:param name="XML" select ="''" />
<xsl:variable name ='inputMessage' select='userCSharp:convertXMLStringToXPathNodeIterator($XML)' />
<xsl:template match="
/">
<xsl:apply-templates select="/s1:schema" />
</xsl:template>
<xsl:template match="/s1:schema">
<xsl:if test="$Output='json'">
<xsl:text>{"type": "array", "items": {"type": "object", "properties": </xsl:text>
</xsl:if>
<xsl:text>{</xsl:text>
<xsl:for-each select="s1:element/s1:annotation/s1:appinfo/s0:properties/s0:property">
<xsl:variable name="name" select="userCSharp:getName(string(@xpath))" />
<xsl:text>"</xsl:text><xsl:value-of select="$name" /><xsl:text>": </xsl:text>
<xsl:choose>
<xsl:when test="$Output='json'">
<xsl:text>{"type": "string"}, "</xsl:text><xsl:value-of select="concat('_', $name)" /><xsl:text>": {"type": "string"}</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:if test="$Output='xpath'">
<xsl:text>"</xsl:text><xsl:value-of select="@xpath" /><xsl:text>"</xsl:text>
</xsl:if>
<xsl:if test="$Output='getvalues'">
<xsl:text>"</xsl:text><xsl:value-of select="userCSharp:getValue($inputMessage, @xpath)" /><xsl:text>", "</xsl:text>
<xsl:value-of select="concat('_', $name)" /><xsl:text>": "</xsl:text>
<xsl:value-of select="@xpath" /><xsl:text>select="@xpa%;font-size:8pt;font-family:'Courier New';color:black;">"</xsl:text>
</xsl:if>
</xsl:otherwise>
</xsl:choose>
<xsl:if test="position() != last()">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:text>}</xsl:text>
<xsl:if test="$Output='json'">
<xsl:text>}}</xsl:text>
</xsl:if>
</xsl:template>
<msxsl:script language="C# <" implements-prefix="userCSharp">
<msxsl:using namespace="System.IO" />
<msxsl:using namespace="System.Xml.XPath" />
<![CDATA[
public string getName(string param1)
{
string localName = "local-name()='";
int nameStart = param1.LastIndexOf(localName) + localName.Length;
int nameFinish = param1.IndexOf("'", nameStart + 1);
string localName = "local-name()='";
int nameStart = param1.LastIndexOf(localName) +e:8pt;font-family:'Courier New';color:gray;">
return param1.Substring(nameStart, nameFinish - nameStart);
}
public object convertXMLStringToXPathNodeIterator(string XML)
{
if (XML != String.Empty)
{
using (StringReader sr = new StringReader(XML))
{
XPathDocument xpd = new XPathDocument(sr);
XPathNavigator xpn = xpd.CreateNavigator();
return xpn.Select("/");
}
}
else
{
return String.Empty;
}
}
public string getValue(XPathNavigator xpn, string xPath)
{
XPathNodeIterator xni = xpn.Select(xPath);
try
{
xni.MoveNext();
return xni.Current.Value;
}
catch(System.Exception ex)
{
return String.Empty;
}
}
]]></msxsl:script>
</xsl:stylesheet>
5. Create a new file project.json and upload the file to the root folder (i.e. the same folder as run.cx). Adding this file will cause the relevant NuGet packages to be loaded. The project.json file should contain the following:
{
Create a new file