BizTalk : How To : Updating repeating nodes in an XML Document using XPathMutatorStream

Wednesday, December 19, 2012

Post by : Thiago Almedia

Scenario:  I often have to set the value of an element under a message’s repeating node with the same value. what if the value isn’t in the source message?

Of course, normally you’d use a map, but what if the value isn’t in the source message? The last time I ran into this was in a project where orders coming into BizTalk via a web service had to be inserted into an AS400 system. The way the already existing AS400 tables were designed to link the order header and the order details was to have the same GUID inserted into both tables, so the order items schema had a GUID field for each line of the order.
 I couldn’t use a distinguished field to set the value of the GUID in the order lines schema. This is, of course, because it’s under a repeating node. Even if you have a second message with the the value (say, for example, you use the SQL Adapter and a stored procedure to insert the order header and it returns the newly generated ID in a message), you would then have to use a map with multiple inputs… So it ocurred to me that this could be a great opportunity for the XPathMutatorStream class! This class is usually used in custom pipeline components. It has been blogged about by Martijn Hoogendoorn andPatrick Wellink amongst others and I wanted to share how I used it. Say we have the following schemas and map:
XPathMutatorStream Map
And we make sure the WEBGUID element on the destination schema is populated with a blank value by selecting it and setting its “Value” property to <empty> (this will create the element with an empty value on the destination message):
 Then, I created the following helper c# class library to perform the update. Don’t forget to reference Microsoft.Biztalk.Streaming and Microsoft.BizTalk.XPathReader (get them from the GAC). 
using System;using System.Xml;using System.IO;using Microsoft.BizTalk.XPath;using Microsoft.BizTalk.Streaming; namespace MyCompanyUtilities{    [Serializable]    public class XPathUtil    {         private string newValue = “”;          public string NewValue        {            get { return newValue; }            set { newValue = value; }        }         private string xpathToUpdate = “”;         public string XpathToUpdate        {            get { return xpathToUpdate; }            set { xpathToUpdate = value; }        }         //Updates an xml document with all matches in an xpath with a value        public XmlDocument updateWithXpath(XmlDocument xDoc)        {            //Check for valid arguments            if (newValue == “”)                throw new ArgumentException(“NewValue”);            if (xpathToUpdate == “”)                throw new ArgumentException(“XpathToUpdate”);             XmlDocument retXml = new XmlDocument();             //Setup the stream and rewind            using (MemoryStream memstream = new MemoryStream())            {                //Copy xml document to memory stream and rewind                xDoc.Save(memstream);                memstream.Position = 0;                 //Define the XPathMutator                XPathCollection queries = new XPathCollection();                queries.Add(new XPathExpression(xpathToUpdate));                ValueMutator xpathMatcher = new ValueMutator(this.XPathCallBack);                 //Get resulting stream into response xml                retXml.Load(new XPathMutatorStream(memstream, queries, xpathMatcher));            }             return retXml;        }         private void XPathCallBack(int matchIdx, XPathExpression matchExpr, string origVal, ref string finalVal)        {            finalVal = newValue;        }    }}
 So from there it was just a case of referencing the c# class library from my BizTalk solution, and calling the helper class from the orchestration in a construct shape (in the same construction shape as the map). Here’s how the sample orchestration looks like:

XPathMutatorStream Orchestration
  I created a variable in the orchestration for the class above. The “Set GUID” construct shape has the following: 
xpathHelper = new MyCompanyUtilities.XPathUtil();
xpathHelper.XpathToUpdate = “/*[local-name()='Root' and namespace-uri()='http://MyCompany.OrderItems']/*[local-name()='TBWEBITEM' and namespace-uri()='']/*[local-name()='WEBGUID' and namespace-uri()='']“;
xpathHelper.NewValue = msg_WebOrder(BTS.MessageID);
msg_OrderItems = xpathHelper.updateWithXpath(msg_OrderItems);
  So when I input the following message: 
<ns0:Root xmlns:ns0=http://MyCompany.WebOrder>
  <WebOrder>
    <OrderHeader>
      <OrderNumber>1</OrderNumber>
    </OrderHeader>
    <Items>
      <Item>
        <ItemNumber>23</ItemNumber>
        <ItemDescription>Cheese</ItemDescription>
        <Quantity>10</Quantity>
        <UnitPriceExcTax>5.4</UnitPriceExcTax>
      </Item>
      <Item>
        <ItemNumber>14</ItemNumber>
        <ItemDescription>Ham</ItemDescription>
        <Quantity>10</Quantity>
        <UnitPriceExcTax>4.0</UnitPriceExcTax>
      </Item>
      <Item>
        <ItemNumber>12</ItemNumber>
        <ItemDescription>Bread</ItemDescription>
        <Quantity>1</Quantity>
        <UnitPriceExcTax>3.30</UnitPriceExcTax>
      </Item>
    </Items>
  </WebOrder>
</ns0:Root>
 The output message is: 
<ns0:Root xmlns:ns0=http://MyCompany.OrderItems>
  <TBWEBITEM>
    <WEBGUID>d181486e-649b-4d29-87ab-a811bb175ef3</WEBGUID>
    <WEBITMNBR>23</WEBITMNBR>
    <WEBITMDSC>Cheese</WEBITMDSC>
    <WEBQTY>10</WEBQTY>
    <WEBPRC>5.4</WEBPRC>
  </TBWEBITEM>
  <TBWEBITEM>
    <WEBGUID>d181486e-649b-4d29-87ab-a811bb175ef3</WEBGUID>
    <WEBITMNBR>14</WEBITMNBR>
    <WEBITMDSC>Ham</WEBITMDSC>
    <WEBQTY>10</WEBQTY>
    <WEBPRC>4.0</WEBPRC>
  </TBWEBITEM>
  <TBWEBITEM>
    <WEBGUID>d181486e-649b-4d29-87ab-a811bb175ef3</WEBGUID>
    <WEBITMNBR>12</WEBITMNBR>
    <WEBITMDSC>Bread</WEBITMDSC>
    <WEBQTY>1</WEBQTY>
    <WEBPRC>3.30</WEBPRC>
  </TBWEBITEM>
</ns0:Root>

 So the helper class updated all the WEBGUID elements with the same value without us having to use a map with multiple inputs, or performing a loop inside the orchestration, or browsing through the message using XpathNavigator. Of course you can use the XPathMutatorStream with any repeating node you want or with a list of different xpath queries that will match somewhere in the message. You can also add some funky logic in the XPathCallBack method to have it use different values depending on the original value.

Dowload the sample here.


Note: I could have also used a custom pipeline functoid to get the Message ID from the source schema by using the “Custom GetContextProperty functoid“.

No comments:

Post a Comment

Post Your Comment...