Using BizTalk xpath() Function to Retrieve Optional Nodes
April 8, 2013 1 Comment
I often suffer a bit of trepidation when trying to retrieve values for optional XML nodes using the built-in xpath() function in an orchestration, as I wonder if it will result in an exception of some kind if the node isn’t found in the message. This concern is heightened by reading Microsoft’s own documentation of the xpath function on MSDN:
The engine is not schema-aware, so you can only read values from or write values to a node that exists in the containing message (the complete path must exist), or the engine will raise an exception. This is true even if you supply a default value.
The conclusion from this is that if the node doesn’t exist in the message instance, you’ll get a runtime error trying to read from it using xpath(), right?
Wrong! In fact, as Leonid Ganeline proved in an older blog post, you will either get an empty string or “null”, depending on whether you wrap the XPATH expression in a string() function or not:
- xpath(myMessage, “//*[local-name()=’nonExistentNode’]/text()“) –> null
- xpath(myMessage, “string(//*[local-name()=’nonExistentNode’])“) –> String.Empty
Note the use of the text() function in the first option. Failure to use this would cause an InvalidOperationException if the node were actually present, because you are trying to assign an XmlNode to a string. Another option to using the text() function would be to wrap the result in System.Convert.ToString(); however, this will return an empty string instead of null if the node isn’t present.
If your types are not strings, then you would probably use a boolean() or a number() function instead of string(). However, be prepared for Invalid Format errors if the type conversion fails!
For most cases, the safest option would probably be to always wrap the result in System.Convert.ToString(), like so:
strValue = System.Convert.ToString(
xpath(myMessage, “//*[local-name()=’myOptionalField’]”));
By assigning this to a string, you can then avoid most invalid format errors and test the result yourself. You can also optionally choose to use the string() function inside the xpath() function, regardless of the actual datatype, as this will convert invalid boolean or number types to an empty string:
strValue = System.Convert.ToString( xpath(myMessage,
“string(//*[local-name()=’myOptionalField’])“));
The table below shows the results of different scenarios using this approach. In all cases, the result of the xpath() function was wrapped inside a System.Convert.ToString() method and assigned to a string variable:
Schema Node Data Type | XPath expression wrapped? | Instance Node State | |||
---|---|---|---|---|---|
Present with value | Empty node | Null (xs:nil = true) | No node (missing) | ||
xs:string | No | *see below | “” | “” | “” |
string() | value | “” | “” | “” | |
xs:int | No | *see below | “” | “” | “” |
number() | value | NaN | NaN | NaN | |
string() | value | “” | “” | “” | |
xs:boolean | No | *see below | “” | “” | “” |
boolean() | **True | True | True | False | |
string() | value | “” | “” | “” |
*returned “Microsoft.XLANGs.Core.Part+ArrayBasedXmlNodeList”
**Always returned “True”, no matter what value was entered (-1, 0, 1, true, false, blah, etc.)
For those that are interested, here is the code in my expression shape that I tested with:
strValue = System.Convert.ToString(xpath(Message_1, “//*[local-name()=’OptionalField1′]”));
System.Diagnostics.Trace.WriteLine(“INFO: Value: ” + strValue);if(strValue == null){ System.Diagnostics.Trace.WriteLine(“INFO: [Value was null]”); }
if(strValue == System.String.Empty){ System.Diagnostics.Trace.WriteLine(“INFO: [Value was empty string]”); }strValue = System.Convert.ToString(xpath(Message_1, “string(//*[local-name()=’OptionalField1′])”));
System.Diagnostics.Trace.WriteLine(“INFO: Value (using string()): ” + strValue);if(strValue == null){ System.Diagnostics.Trace.WriteLine(“INFO: [Value was null]”); }
if(strValue == System.String.Empty){ System.Diagnostics.Trace.WriteLine(“INFO: [Value was empty string]”); }System.Diagnostics.Trace.WriteLine(“INFO: ——————————————————–“);
strValue = System.Convert.ToString(xpath(Message_1, “//*[local-name()=’OptionalIntField1′]”));
System.Diagnostics.Trace.WriteLine(“INFO: Value: ” + strValue);if(strValue == null){ System.Diagnostics.Trace.WriteLine(“INFO: [Value was null]”); }
if(strValue == System.String.Empty){ System.Diagnostics.Trace.WriteLine(“INFO: [Value was empty string]”); }strValue = System.Convert.ToString(xpath(Message_1, “number(//*[local-name()=’OptionalIntField1′])”));
System.Diagnostics.Trace.WriteLine(“INFO: Value (using number()): ” + strValue);if(strValue == null){ System.Diagnostics.Trace.WriteLine(“INFO: [Value was null]”); }
if(strValue == System.String.Empty){ System.Diagnostics.Trace.WriteLine(“INFO: [Value was empty string]”); }strValue = System.Convert.ToString(xpath(Message_1, “string(//*[local-name()=’OptionalIntField1′])”));
System.Diagnostics.Trace.WriteLine(“INFO: Value (using string()): ” + strValue);if(strValue == null){ System.Diagnostics.Trace.WriteLine(“INFO: [Value was null]”); }
if(strValue == System.String.Empty){ System.Diagnostics.Trace.WriteLine(“INFO: [Value was empty string]”); }System.Diagnostics.Trace.WriteLine(“INFO: ——————————————————–“);
strValue = System.Convert.ToString(xpath(Message_1, “//*[local-name()=’OptionalBoolField1′]”));
System.Diagnostics.Trace.WriteLine(“INFO: Value: ” + strValue);if(strValue == null){ System.Diagnostics.Trace.WriteLine(“INFO: [Value was null]”); }
if(strValue == System.String.Empty){ System.Diagnostics.Trace.WriteLine(“INFO: [Value was empty string]”); }strValue = System.Convert.ToString(xpath(Message_1, “boolean(//*[local-name()=’OptionalBoolField1′])”));
System.Diagnostics.Trace.WriteLine(“INFO: Value (using boolean()): ” + strValue);if(strValue == null){ System.Diagnostics.Trace.WriteLine(“INFO: [Value was null]”); }
if(strValue == System.String.Empty){ System.Diagnostics.Trace.WriteLine(“INFO: [Value was empty string]”); }strValue = System.Convert.ToString(xpath(Message_1, “string(//*[local-name()=’OptionalBoolField1′])”));
System.Diagnostics.Trace.WriteLine(“INFO: Value (using string()): ” + strValue);if(strValue == null){ System.Diagnostics.Trace.WriteLine(“INFO: [Value was null]”); }
if(strValue == System.String.Empty){ System.Diagnostics.Trace.WriteLine(“INFO: [Value was empty string]”); }System.Diagnostics.Trace.WriteLine(“INFO: ========================================================”);
For more information and resources about the BizTalk xpath() function, see the BizTalk Orchestrations XPath Survival Guide on TechNet.
I hope this helps instil some confidence in using the xpath() function in your orchestrations, as it is often the simplest and most powerful way to access message content, particularly for repeating nodes where distinguished fields are not allowed.
Pingback: BizTalk – Decide on XPath xs:boolean | My Blog.