Sometimes I ask myself: Why is so hard to make a simple If-Then-Else Functoid, or even so painful to do an If-Then-Else operation, using BizTalk mapper?
I don’t mean to say that it is complicated, quite the opposite is quite easy to make If…Then…Else statements using the Mapper. You can use If…Then…Else statements, to be completely correct, you can use something related to an If…Then…Else statements, to execute blocks of statements depending on the Boolean value of a condition by, normally, using:
- One Logical Functoid (Logical Existence, Logical String, Logical Numeric, Equal, Greater Than, Less Than and so on) to determine:
- whether the Record, Field Element, or Field Attribute node that is linked to it exists or have a valid value for a particular input instance message
- or if a condition match.
- One Logical NOT Functoid to negate the Logical Functoid
- And two Value Mapping Functoids to returns the value that we want to link based on the result of the condition
In this sample, if the “Operation” element is equal to “Create”
- Then: you will map the value of the element “ValueA” from the source schema to the element “Result” in the destination schema
- Else (otherwise): you will map the value of the element “ValueB” from the source schema to the element “Result” in the destination schema
So, this functoid chain will provide a secondary path of execution when an “if” clause evaluates to false, the Else path. I said earlier: “something related to an If…Then…Else statement” because, programming speaking, If…Then…Else statements are implemented by:
//C# if(condition) { //something if true } else { //something if false }
Or
<!-- XSLT --> <xsl:choose> <xsl:when test="expression"> ... something if true ... </xsl:when> <xsl:otherwise> ... something if false ... </xsl:otherwise> </xsl:choose>
But in fact, what this functoid chain does is two If statements
<!-- something if true --> <xsl:if test="string($var:v1)='true'"> <xsl:variable name="var:v2" select="ValueA/text()" /> <Result> <xsl:value-of select="$var:v2" /> </Result> </xsl:if> <!-- something if false (var:v3 contains the negation of var:v1) --> <xsl:if test="string($var:v3)='true'"> <xsl:variable name="var:v4" select="ValueB/text()" /> <Result> <xsl:value-of select="$var:v4" /> </Result> </xsl:if>
This approach is good for small messages, but even with small messages, the Mapper will add many unnecessary code and operations. Because of that, I decided to create an If-Then-Else Functoid to use in these situations and improve a little more the performance of the map, of course, undoubtedly, that the best option and with most performance to make conditions is using custom XSLT code (but this is another story).
The annoying things in using this out-of-the-box functoids are:
- We get these annoying warnings (but they are useful in many scenarios) when we validate the maps:
- warning btm1004: The destination node “Result” has multiple inputs. For a destination node to have multiple inputs, one of its ancestors should be connected to a looping functoid.
- And for a simple if-then-else operation we need to use a minimum of 4 functoids. If we have several conditions inside the map, it will be easily filled with functoids and as a result, will become a little confused and sometimes difficult to manage.
- And, obviously, we will have several unnecessary operations.
So, why is so hard to make an If-Then-Else Functoid?
What I want to archive is create a custom functoid that accepts 3 inputs:
- A Boolean – the result of a previous Logical Functoid (Logical Existence, Logical String, Logical Numeric, Equal, Greater Than, Less Than and so on)
- And two inputs
Were the custom Functoid will return a value from one of two input parameters based on a condition.
- If the condition (first input) is True, then the value of the second input parameter is returned;
- Otherwise, the Third input is returned.
Translating to C# code, it will be something like this:
public string IfThenElseOperation(string condition, string trueValue, string falseValue) { if (System.Convert.ToBoolean(condition)) return trueValue; return falseValue; }
Note: I will not address this topic here, this is content deserved a completely different post and dedicated to the topic, but the best option that you have is creating a custom functoid belonging to the String functoid category (if you like it, put a comment in the post and I will address this topic in another time/post).
However, if you do that, you will find that out-of-the-box, it is impossible to create a custom functoid based on a Logical Functoid and the reason why this is true is that all Logical Functoids available out-of-the-box with BizTalk only accept the following outputs connection types:
- ConnectionType.Element
- ConnectionType.FunctoidAssert
- ConnectionType.FunctoidNilValue
- ConnectionType.FunctoidKeyMatch
- ConnectionType.FunctoidTableLooping
- ConnectionType.FunctoidValueMapping
- ConnectionType.FunctoidScripter
- ConnectionType.FunctoidLogical;
ConnectionType Enumeration: Specifies the types of connections that can be used as inputs or outputs for a functoid. This enumeration has a FlagsAttribute attribute that allows a bitwise combination of its member values.
ConnectionType.FunctoidString is not allowed in all the existing Logical Functoids. And that is the reason why you will find impossible to create a custom functoid based on a Logical functoid (don’t know the reason why Microsoft decided to implement this limitation)
How did I solve (or I overcame) this limitation?
To solve (workaround) this limitation, so that I could create and use a custom if-then-else functoid, at the same time be fully compatible with existing functoids and don’t produce any more additional code, I was forced to create my own personal custom Logical Functoids:
- Advance Logical AND Functoid
- Advance Equal Functoid
- Advance Greater Than Functoid
- Advance Greater Than or Equal To Functoid
- Advance Less Than Functoid
- Advance Less Than or Equal To Functoid
- Advance Not Equal Functoid
- Advance Logical NOT Functoid
- Advance Logical OR Functoid
That has the same behavior as the out-of-the-box Logical Functoids:
- Logical AND Functoid
- Equal Functoid
- Greater Than Functoid
- Greater Than or Equal To Functoid
- Less Than Functoid
- Less Than or Equal To Functoid
- Not Equal Functoid
- Logical NOT Functoid
- Logical OR Functoid
with the advantage that also accepts the String output connection type.
base.OutputConnectionType = ConnectionType.FunctoidString | ConnectionType.Element | ConnectionType.FunctoidAssert | ConnectionType.FunctoidNilValue | ConnectionType.FunctoidKeyMatch | ConnectionType.FunctoidTableLooping | ConnectionType.FunctoidValueMapping | ConnectionType.FunctoidScripter | ConnectionType.FunctoidLogical;
By doing that, I can create my if-then-else custom functoid:
namespace BizTalk.CustomAdvanced.Functoids { [Serializable] public class IfThenElse : BaseFunctoid { public IfThenElse() : base() { //ID for this functoid this.ID = 10900; // resource assembly must be ProjectName.ResourceName if building with VS.Net SetupResourceAssembly("BizTalk.Logical.Functoids.LogicalResources", Assembly.GetExecutingAssembly()); //Setup the Name, ToolTip, Help Description, and the Bitmap for this functoid SetName("IDS_IFELSEFUNCTOID_NAME"); SetTooltip("IDS_IFELSEFUNCTOID_TOOLTIP"); SetDescription("IDS_IFELSEFUNCTOID_DESCRIPTION"); SetBitmap("IDS_IFELSEFUNCTOID_BITMAP"); //category for this functoid. This functoid goes under the String Functoid Tab in the this.Category = FunctoidCategory.String; // Set the limits for the number of input parameters. This example: 1 parameter this.SetMinParams(3); this.SetMaxParams(3); // Add one line of code as set out below for each input param. For multiple input params, each line would be identical. this.AddInputConnectionType(ConnectionType.AllExceptRecord); //first input this.AddInputConnectionType(ConnectionType.AllExceptRecord); //Second input this.AddInputConnectionType(ConnectionType.AllExceptRecord); //Third input // The functoid output can go to any node type. this.OutputConnectionType = ConnectionType.AllExceptRecord; SetScriptBuffer(ScriptType.CSharp, this.GetCSharpBuffer()); HasSideEffects = false; } private string GetCSharpBuffer() { StringBuilder builder = new StringBuilder(); builder.Append("public string IfThenElseOperation(string condition, string trueValue, string falseValue)\n"); builder.Append("{\n"); builder.Append("\tif (System.Convert.ToBoolean(condition))\n"); builder.Append("\t\treturn trueValue;\n"); builder.Append("\treturn falseValue;\n"); builder.Append("}\n"); return builder.ToString(); } } }
And have the ability to connect my logical functoids to a custom String functoid. Of course, you need to use it wisely!
The beauty of this approach is that:
- We reduce the functoids chain. By using fewer functoids we simplify the map visually, and this is very useful when you have a lot of conditions, even if they are simple conditions.
- We will improve the map performance a little more, compared to the use of out-of-the-box functoids
- Using Custom If-Then-Else Functoid:
&lt;xsl:variable name="var:v1" select="userCSharp:LogicalEq(string(Operation/text()) , 'Create')" /&gt; &lt;xsl:variable name="var:v2" select="userCSharp:IfThenElseOperation(string($var:v1) , string(ValueA/text()) , string(ValueB/text()))" /&gt; &lt;ns0:Output&gt; &lt;Result&gt; &lt;xsl:value-of select="$var:v2" /&gt; &lt;/Result&gt;; &lt;/ns0:Output&gt; public bool LogicalEq(string val1, string val2) { bool ret = false; double d1 = 0; double d2 = 0; if (IsNumeric(val1, ref d1) &amp;amp;amp;amp;&amp;amp;amp;amp; IsNumeric(val2, ref d2)) { ret = d1 == d2; } else { ret = String.Compare(val1, val2, StringComparison.Ordinal) == 0; } return ret; } public string IfThenElseOperation(string condition, string trueValue, string falseValue) { if (System.Convert.ToBoolean(condition)) return trueValue; return falseValue; } public bool IsNumeric(string val) { if (val == null) { return false; } double d = 0; return Double.TryParse(val, System.Globalization.NumberStyles.AllowThousands | System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out d); } public bool IsNumeric(string val, ref double d) { if (val == null) { return false; } return Double.TryParse(val, System.Globalization.NumberStyles.AllowThousands | System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out d); }
- Using Out-of-the-box Functoids:
&lt;xsl:variable name="var:v1" select="userCSharp:LogicalEq(string(Operation/text()) , 'Create')" &gt; &lt;xsl:variable name="var:v3" select="userCSharp:LogicalNot(string($var:v1))" &gt; &lt;ns0:Output&gt; &lt;xsl:if test="string($var:v1)='true'"&gt; &lt;xsl:variable name="var:v2" select="ValueA/text()" /&gt; &lt;Result&gt; &lt;xsl:value-of select="$var:v2" /&gt; &lt;/Result&gt; &lt;/xsl:if&gt; &lt;xsl:if test="string($var:v3)='true'"&gt; &lt;xsl:variable name="var:v4" select="ValueB/text()" /&gt; &lt;Result&gt; &lt;xsl:value-of select="$var:v4" /&gt; &lt;Result&gt; &lt;xsl:if&gt; &lt;ns0:Output&gt; public bool LogicalEq(string val1, string val2) { bool ret = false; double d1 = 0; double d2 = 0; if (IsNumeric(val1, ref d1) &amp;amp;amp;amp;&amp;amp;amp;amp; IsNumeric(val2, ref d2)) { ret = d1 == d2; } else { ret = String.Compare(val1, val2, StringComparison.Ordinal) == 0; } return ret; } public bool LogicalNot(string val) { return !ValToBool(val); } public bool IsNumeric(string val) { if (val == null) { return false; } double d = 0; return Double.TryParse(val, System.Globalization.NumberStyles.AllowThousands | System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out d); } public bool IsNumeric(string val, ref double d) { if (val == null) { return false; } return Double.TryParse(val, System.Globalization.NumberStyles.AllowThousands | System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out d); } public bool ValToBool(string val) { if (val != null) { if (string.Compare(val, bool.TrueString, StringComparison.OrdinalIgnoreCase) == 0) { return true; } if (string.Compare(val, bool.FalseString, StringComparison.OrdinalIgnoreCase) == 0) { return false; } val = val.Trim(); if (string.Compare(val, bool.TrueString, StringComparison.OrdinalIgnoreCase) == 0) { return true; } if (string.Compare(val, bool.FalseString, StringComparison.OrdinalIgnoreCase) == 0) { return false; } double d = 0; if (IsNumeric(val, ref d)) { return (d &amp;amp;amp;gt; 0); } } return false; }
- You don’t need to deploy any of these custom functoids (advanced logical functoids or/and if-the-else functoid) to your production environment, you only need to have then in your development environment to be used inside Visual Studio because all of them are custom inline functoids, i.e., they will add the necessary code inside the XSLT file:
- And finally, you can use all the custom advance Logical Functoids allow side with the out-of-the-box Logical Functoids without any impact and without producing additional code, because they will produce the exact same code of out-of-the-box Logical Functoids!
Generated code:
<!-- Custom Functoid -->; <xsl:variable name="var:v1" select="userCSharp:LogicalEq(string(Operation/text()) , 'Create')" />; <xsl:variable name="var:v2" select="userCSharp:IfThenElseOperation(string($var:v1) , string(ValueA/text()) , string(ValueB/text()))" />; <!-- out-of-the-box Functoids -->; <xsl:variable name="var:v3" select="string(Operation/text())" />; <xsl:variable name="var:v4" select="userCSharp:LogicalEq($var:v3 , 'Create')" />; <xsl:variable name="var:v6" select="userCSharp:LogicalNot(string($var:v4))" />; <ns0:Output>; <!-- Custom Functoid -->; <Result>; <xsl:value-of select="$var:v2" />; </Result>; <!-- out-of-the-box Functoids -->; <xsl:if test="string($var:v4)='true'">; <xsl:variable name="var:v5" select="ValueA/text()" />; <Total>; <xsl:value-of select="$var:v5" />; </Total>; </xsl:if>; <xsl:if test="string($var:v6)='true'">; <xsl:variable name="var:v7" select="ValueB/text()" />; <Total>; <xsl:value-of select="$var:v7" />; </Total>; </xsl:if>; </ns0:Output>; </xsl:template>; <!-- Both out-of-the-box Functoids and custom functoids will produce this LogicalEq code! -->; public bool LogicalEq(string val1, string val2) { bool ret = false; double d1 = 0; double d2 = 0; if (IsNumeric(val1, ref d1) &amp;amp;amp;&amp;amp;amp; IsNumeric(val2, ref d2)) { ret = d1 == d2; } else { ret = String.Compare(val1, val2, StringComparison.Ordinal) == 0; } return ret; } public string IfThenElseOperation(string condition, string trueValue, string falseValue) { if (System.Convert.ToBoolean(condition)) return trueValue; return falseValue; } public bool LogicalNot(string val) { return !ValToBool(val); } public bool IsNumeric(string val) { if (val == null) { return false; } double d = 0; return Double.TryParse(val, System.Globalization.NumberStyles.AllowThousands | System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out d); } public bool IsNumeric(string val, ref double d) { if (val == null) { return false; } return Double.TryParse(val, System.Globalization.NumberStyles.AllowThousands | System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out d); } public bool ValToBool(string val) { if (val != null) { if (string.Compare(val, bool.TrueString, StringComparison.OrdinalIgnoreCase) == 0) { return true; } if (string.Compare(val, bool.FalseString, StringComparison.OrdinalIgnoreCase) == 0) { return false; } val = val.Trim(); if (string.Compare(val, bool.TrueString, StringComparison.OrdinalIgnoreCase) == 0) { return true; } if (string.Compare(val, bool.FalseString, StringComparison.OrdinalIgnoreCase) == 0) { return false; } double d = 0; if (IsNumeric(val, ref d)) { return (d &amp;amp;gt; 0); } } return false; }
May not be the perfect solution, I agree with that but I found a very interesting and useful approach for many scenarios. I hope you enjoy it because I have been used it a lot lately 🙂
The functoids will be available in the next version of BizTalk Mapper Extensions UtilityPack, until then you have all the source code and samples here:
Download
BizTalk Mapper: How to create a custom If-Then-Else Functoid
GitHub