BizTalk: Auto-generate BTDF PortbindingsMaster.xml using XSLT
Introduction
Suppose you use the PortBindingMaster.xml (for details see here BTDF PortbindingsMaster or BTDF to create alternative ports ) to configure environment specific settings for BizTalk port bindings.
A common way of creating the PortBindingMaster.xml is to export the relevant BizTalk binding and manually find and replace environment specific values with tags ${<VariableName>}.
When you deploy your BizTalk application and start testing, you will often have to fine tune some settings (e.g. timeouts, transaction isolation level). This might mean that you may have to repeatedly export bindings and manually replace values with corresponding variable name tags for your port binding’s master file to redeploy your solution.
This post shows you how to use XSLT to automate "find and replace” in your port bindings file to create port bindings master file.
Applies To
BTDF 5.0 - 5.6 RC
VisualStudio 2013
MsBuild 12.0
XSLT 1.0
Sample XSL
This sample XSL replace all occurrences of servername, userid and password from a given portbindings.xml. There are many transformations you can do with xslt , this sample shows you how to get started and covers some very common replacement scenarios.
**NOTE: Bug with Code display block below. In example 3, all xml special characters within single quotes, say '<UserName vt="8">ContosoUser</UserName>' are actually escaped as '<UserName vt="8">ContosoUser</UserName>' .
<? xml version="1.0" encoding="utf-8"?>
< xsl:stylesheet version="1.0"
xmlns:xsl = " http://www.w3.org/1999/XSL/Transform " xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="user" xmlns:user="urn:my-scripts">
< xsl:output method="xml" encoding="utf-8" version="1.0" indent="yes" omit-xml-declaration="no" />
<!--Note: This xslt custom .net functions (see msxsl:script section at the end) as xslt 1.0 does not provide builtin replace functions -->
<!-- Step 1: Match Root -->
< xsl:template match="/">
<!-- Copy creates a node in the output with the same name, namespace, and type as the current node. Attributes and children are not copied automatically -->
< xsl:copy >
< xsl:apply-templates select="@*|node()"/>
</ xsl:copy >
</ xsl:template >
<!-- Step 2: Match any node or attribute -->
< xsl:template match="@*|node()">
< xsl:copy >
< xsl:apply-templates select="@*|node()"/>
</ xsl:copy >
</ xsl:template >
<!-- Step 3: Example 1: Match all text nodes and replace all occurrences of CONTOSODEV01 in text nodes with replacement string tag ${ContosoDatabaseServer}. -->
< xsl:template match="text()[user:caseinsenstivecontains(., 'CONTOSODEV01')]">
< xsl:value-of select="user:caseinsenstivereplace(.,' CONTOSODEV01', '${ContosoDatabaseServer}')"/>
</ xsl:template >
<!-- Step 3: Example 2: Match all text nodes and replaces username ContosoUser and corresponding passwords with ${ContosoDatabaseServerUser} ${ContosoDatabaseServerUserPwd}. -->
< xsl:template match="text()[contains(., '<UserName vt="8">ContosoUser</UserName>')]">
<!--This is the match string for <UserName vt="8">ContosoUser</UserName>, which is the TransportTypeData custom props in for say WCF-SQl binding-->
< xsl:variable name="userNameMarker" select="'<UserName vt="8">ContosoUser</UserName>'"/>
<!--This is the replacement for username and looks like <UserName vt="8">${ContosoDatabaseServerUser}</UserName> -->
< xsl:variable name="userNameMarkerReplacement" select="'<UserName vt="8">${ContosoDatabaseServerUser}</UserName>'"/>
<!--This is the match string for <Password vt="1" /> for the corresponding password in TransportTypeData custom props -->
< xsl:variable name="passwordMarker" select="'<Password vt="1" />'"/>
<!--This is the replacement string for password and looks like <Password vt="8">${ContosoDatabaseServerUserPwd}</Password >. Note vt=1 is replaced with vt=8 to let biztalk know that the password value is populated. -->
< xsl:variable name="passwordMarkerReplacement" select="'<Password vt="8">${ContosoDatabaseServerUserPwd}</Password >'"/>
< xsl:value-of select="user:caseinsenstivereplace(user:caseinsenstivereplace(.,$userNameMarker,$userNameMarkerReplacement),$passwordMarker, $passwordMarkerReplacement)" />
</ xsl:template >
<!-- Step 3: Example 3: Attribute match "Name" to replace the receive handler for WCF-SQL for different environments -->
< xsl:template match="@Name[parent::ReceiveHandler[TransportType/@Name='WCF-SQL']]">
< xsl:variable name="handler" select="'${WCFSqlReceiveHandler}'"/>
< xsl:attribute name="Name">
< xsl:value-of select="$handler"/>
</ xsl:attribute >
</ xsl:template >
<!--Custom .net functions to replace and match string-->
< msxsl:script language="C#" implements-prefix="user">
< msxsl:using namespace="System" />
< msxsl:using namespace="System.Text.RegularExpressions" />
<![CDATA[
//Just some simple .net string match and replacement functions
public bool caseinsenstivecontains(string mystring, string substring)
{
return mystring.ToLower().Contains(substring.ToLower());
}
public string replace(string mystring, string oldstring, string newstring)
{
return mystring.Replace(oldstring, newstring);
}
public string caseinsenstivereplace(string mystring, string oldstring, string newstring)
{
return Regex.Replace( mystring, oldstring, newstring, RegexOptions.IgnoreCase);
}
]]>
</ msxsl:script >
</ xsl:stylesheet >
MsBuild task for XSLT transformation
You can use the MsBuild XslTransformationTask to transform your portbindings.xml
<XslTransformation XslInputPath=".\TransformToPortbindingsMaster.xslt" XmlInputPaths="..\Bindings\PortBindings.xml" OutputPaths=".\PortBindingsMaster.xml" />
This portbindingsmaster.xml can be generated any time before the BTDF build starts to look for it. If you choose to generate this as part of building your Deployment.btdfproj, then you should include this as part of btdf custom target CustomPreRedist as shown below
<!--Customization point, builtin BTDF target-->
< Target Name="CustomPreRedist">
< XslTransformation XslInputPath=".\TransformToPortbindingsMaster.xslt" XmlInputPaths="..\Bindings\PortBindings.xml" OutputPaths=".\PortBindingsMaster.xml" />
</ Target >
Testing your XSLT in visual studioYou can test you xslt to generate the transformed PortBindingsMaster.xml file in visual studio as shown below.
You can also compare your port bindings.xml with the transformed port bindings master file within visual studio using the Tools.DiffFiles through the visual studio command window (Main Menu View-> Other Windows->Command Window)
Tools.DiffFiles C:\Bindings\SampleApp.BindingInfo.xml C:\Users\ * * * * \AppData\Local\Temp\ 4 \TransformToPortbindingsMaster.xml
See Also
Another important place to find a huge amount of BizTalk related articles is the TechNet Wiki itself. The best entry point is BizTalk Server Resources on the TechNet Wiki.