BizTalk: Using XPath expressions
Introduction
XPath can provide a valuable and efficient way to retrieve values from BizTalk messages, especially when you can't use distinguished fields, for example in looping records. However, it can be quite a complicated task as well, to find out how to retrieve a certain value. To that end, this post contains a list of XPath filter expressions commonly used in BizTalk Server. Unless specified differently, these will all return a node set, which can be loaded into an XML element. In all the sample statement we will use the XML below.
<Order> <Delivery> <DeliveryType>Home</DeliveryType> <BoxInfo> <BoxID>22</BoxID> </BoxInfo> </Delivery> <Delivery> <DeliveryType>Work</DeliveryType> <BoxInfo> <BoxID>35</BoxID> </BoxInfo> </Delivery> <Delivery> <DeliveryType>Home</DeliveryType> <BoxInfo> <BoxID>12</BoxID> <BoxID>87</BoxID> </BoxInfo> </Delivery> <Boxes> <Box material="cardboard"> <ID>12</ID> <Contents>Envelopes</Contents> </Box> <Box material="cardboard"> <ID>22</ID> <Contents>Surface Pro 2</Contents> </Box> <Box material="plastic"> <ID>35</ID> <Contents>Stickers</Contents> </Box> <Box material="cardboard"> <ID>87</ID> <Contents>Stamps</Contents> </Box> </Boxes> </Order>
Filter on index
The first option is to filter using indexes. Beware XPath indexing starts at 1, unlike C# indexes.
Samples
Get the second delivery node.
/*[local-name()='Order']/*[local-name()='Delivery'][2]
Get the deliverytype node of the second delivery.
/*[local-name()='Order']/*[local-name()='Delivery'][2]/*[local-name()='DeliveryType']
Filter on subnode text
You can also use filters on the texts of the nodes. This will return all nodes where the expressed filter is met.
Samples
Get all delivery nodes, which have a deliverytype of Home.
/*[local-name()='Order']/*[local-name()='Delivery'][*[local-name()='DeliveryType'][text()='Home']]
Get the deliverytype node of the delivery for boxid 87.
/*[local-name()='Order']/*[local-name()='Delivery'][*[local-name()='BoxInfo']/*[local-name()='BoxID'][text()='87']]/*[local-name()='DeliveryType']
Get the deliverytype node, of the boxes which contains the stickers.
/*[local-name()='Order']/*[local-name()='Delivery'][*[local-name()='BoxInfo']/*[local-name()='BoxID'][text()=/*[local-name()='Order']/*[local-name()='Boxes']/*[local-name()='Box'][*[local-name()='Contents'][text()='Stickers']]/*[local-name()='ID']]]/*[local-name()='DeliveryType']
Combined filtering
You can also combine various filters in a single expression, making this a very powerful way of getting specific nodes.
Samples
Get the second delivery, where deliverytype is home.
/*[local-name()='Order']/*[local-name()='Delivery'][*[local-name()='DeliveryType'][text()='Home']][2]
Get the delivery, where deliverytype is work, and boxid is 35.
/*[local-name()='Order']/*[local-name()='Delivery'][*[local-name()='BoxInfo']/*[local-name()='BoxID'][text()='35'] and *[local-name()='DeliveryType'][text()='Work']]
Logical operators
As seen in the previous sample, you can also use logical operators like and and or to make your filters more fine-grained.
Samples
Get the deliveries, which contain boxid 12 or boxid 22.
/*[local-name()='Order']/*[local-name()='Delivery'][*[local-name()='BoxInfo']/*[local-name()='BoxID'][text()='12' or text()='22']]
Get node value
In the above examples, you are getting the actual node. Often though, you will want to get the value of a node. This can be done by placing the string or number expression around your XPath.
Samples
Get the value of the contents of the first box as a string.
string(/*[local-name()='Order']/*[local-name()='Boxes']/*[local-name()='Box'][1]/*[local-name()='Contents'])
Get the value of the ID of the second box as a number.
number(/*[local-name()='Order']/*[local-name()='Boxes']/*[local-name()='Box'][2]/*[local-name()='ID'])
Get count of node
Often you will want to count how often a certain node occurs in your message. To do this using XPath, place the count expression around your XPath.
Samples
Count how many deliveries there are.
count(/*[local-name()='Order']/*[local-name()='Delivery'])
Count, how many deliveries with deliverytype home there are.
count(/*[local-name()='Order']/*[local-name()='Delivery'][*[local-name()='DeliveryType'][text()='Home']])
Get distinct values
Sometimes you will want to get the unique (distinct) values of a certain repeating node. This can also be done easily using XPath both for nodes and attributes.
Samples
Get the distinct deliverytypes (in this case Work and Home), note that after preceding-sibling:: we first set the sibling node name, and then the name of the node for which we want to get the values.
/*[local-name()='Order']/*[local-name()='Delivery'][not(*[local-name()='DeliveryType']/text()=preceding-sibling::*[local-name()='Delivery']/*[local-name()='DeliveryType']/text())]/*[local-name()='DeliveryType']
Get the distinct values for the box materials (cardboard and plastic), note that in this case we do not specify the sibling node name after preceding-sibling::, instead we immediately access the attribute.
/*[local-name()='Order']/*[local-name()='Boxes']/*[local-name()='Box'][not(@material=preceding-sibling::*/@material)]/@material
Tools
To avoid having to deploy your projects each time you want to test a change in your XPath, use one of the following tools. These tools especially help out when setting up more complex XPath expressions.
Testing XPath queries in .NET with a tool - Tomasso Groenendijk
Retrieve XML or JSON elements with the XPATH Web API - Apigize
DanSharp XmlViewer - Daniel Probert
See Also
Another important place to find an extensive amount of BizTalk related articles is the TechNet Wiki itself. The best entry point is BizTalk Server Resources on the TechNet Wiki.