For those of you who are unaware, XPath is a powerful query language for XML (ignoring
XQuery for now).
In my experience I’ve worked with a lot of BizTalk developers who have come from a
C# (and usually VB before that) background, and so haven’t always had the background
in XML, XSLT, XSD, and XPath needed to fully appreciate BizTalk.
Because let’s face it: before you were working on BizTalk, how often did you need
to deal directly with Xml? When writing ASP.NET web services, the .NET framework abstracts
away the need to understand XML, leaving you with classes which are handily serialised/de-serialised
across the wire for you.
Note: I have come across a similar pattern in the J2EE world – ask
many Java developers about an Xml document they received as a request in a J2EE Web
Service, and they’ll only know about Beans, as the framework looks after the de-serialization
from Xml into a Bean for them).
However, BizTalk is a different beastie: it works predominantly with data streams
containing Xml.
Therefore the ability to know how to query this Xml without having to resort to C#
code is very very important.
BizTalk 2004 and 2006/2006R2 support XPath 1.0 – as at this time, there is no support
for XPath 2.0 in BizTalk.
You can get a good idea of the functions available in XPath 1.0 here (which
is a useful summary of the W3
Recommendation)
(as a comparison, here’s what XPath
2.0 gives you – here’s hoping we get this in BizTalk sometime soon…!)
For a good primer on XPath, look here.
Every time you use a Distinguished Field in BizTalk, you’re actually using XPath.
Every time you create a map… you’re generating XSLT… which uses XPath.
Using XPath to select an element/attribute value from an Xml instance is very easy.
To see the XPath for the given element/attribute, open the Schema in the BizTalk Schema
Editor, select the element/attribute you want to get a value for, and in the properties
window select the value for Instance XPath:
This will give you an XPath statement something like:
/*[local-name()=’Employees’ and namespace-uri()=”]/*[local-name()=’Employee’
and namespace-uri()=”]
In an orchestration, you can make use of XPath through the use of the xpath() function:
xpath(message, string)
or
xpath(string)
The xpath() function can be used to
both write and read information to/from a message (although to write information to
a message, you need to call it from a Message Assignment shape, and the element/attribute
you’re setting the value of must already exist in the message).
Note that if you’re reading information from a message, you have to cast the result
back to an appropriate type, or use an XmlNode/XmlNodeSet variable.
For example, to get a string value you could use:
myXPath = “/*[local-name()=’Employees’
and namespace-uri()=”]/*[local-name()=’Employee’ and namespace-uri()=”]/@*[local-name()=’status’
and namespace-uri()=”]”;
myValue = System.Convert.ToString(xpath(MyMessage, myXPath));
Note: if you tried the above on an XPath query which returned a node
or node set, then instead of the element value in myValue, you’d get the text “System.Xml.XmlNodeSet” or
similar…
This happens when your XPath selects an element, as opposed to the element’s value
(this doesn’t apply to attributes).
In this case you have to modify your XPath to select the value of the element, instead
of the actual element itself. You can do this by using the text() XPath
function:
/*[local-name()=’Employees’ and namespace-uri()=”]/*[local-name()=’Employee’
and namespace-uri()=”]/@*[local-name()=’status’ and namespace-uri()=”]/text()
The beauty of XPath is that you can use it to operate across node-sets, and then filter
(or query) the results through the use of predicates – similar to using a SQL query
to select values from a database table.
For example, using a predicate of [1] will give you the first occurrence of something
e.g.
/*[local-name()=’Employees’ and namespace-uri()=”]/*[local-name()=’Employee’
and namespace-uri()=”][1]
will give you the first Employee element
Correspondingly, [2] will give you the second, [3] the third, etc.
If you want to get a count of elements, you can use the count() function:
count(/*[local-name()=’Employees’ and namespace-uri()=”]/*[local-name()=’Employee’
and namespace-uri()=”])
And to use this in BizTalk:
myCount = System.Convert.ToInt32(xpath(MyMessage,
“count(/*[local-name()=’Employees’ and namespace-uri()=”]/*[local-name()=’Employee’
and namespace-uri()=”])“))
I use this all the time when I call a web service, and get back a response which contains
a collection of elements and I want to know how many I have.
You can even filter to find the number of elements which have a certain value.
For example, given the following XML:
<Employees >
<Employee id=”1″ status=”Active”>
<FirstName>Sam</FirstName>
<LastName>Smith</LastName>
</Employee>
<Employee id=”2″ status=”Active”>
<FirstName>Jane</FirstName>
<LastName>Smith</LastName>
</Employee>
<Employee id=”2″ status=”Deceased”>
<FirstName>John</FirstName>
<LastName>Doe</LastName>
</Employee>
</Employees>
I might want to get a count of Active employees:
count(/*[local-name()=’Employees’ and namespace-uri()=”]/*[local-name()=’Employee’
and namespace-uri()=”]/@*[local-name()=’status’ and namespace-uri()=”][. = ‘Active’])
Note: because status is
an attribute and not an element, I used “.” to reference the value of the attribute,
rather than text() which I would use
for an element.
In my time I’ve seen a lot of developers create a class representation of a message
(using the xsd.exe tool), and de-serialise
a message into this class just so that they could get a value e.g. a count – why take
the hit of de-serialisation when you can just pull the value out of the message directly?
One of the tricky things about writing XPath queries is that it’s difficult to test
them – you need a tool like Altova
XmlSpy or Stylus Studio which
can test an XPath statement on an instance of an Xml document.
There’s no built-in support in Visual Studio for testing XPath queries.
Which is why I wrote DanSharp
XmlViewer.
This tool lets you get the XPath to select a given element/attribute from an Xml instance
document but more importantly acts as an XPath scratchpad so you can test your XPath
queries:
It even shows you when your syntax is incorrect (as in the above, highlighted red
– you can hover over the text to see the error).
I use this tool all the time to write XPath queries as I can test them before putting
them into BizTalk.
Get the tool here.