I received a great BizTalk question from a customer the other day, and wanted to build out the solution I proposed. Their primary problem was how to correlate an untyped message back to a running orchestration.

There are a few ways you can try and do this, but I wanted to try and stick to the customer requirement of not adding any data to the untyped message itself. Their use case involved receiving a message (of any format), sending it to an MSMQ for pickup, then having an application read the message from the queue, allow the user to act upon it, and then submit the message back to BizTalk via a web service. Interesting.

My solution uses a combination of a property schema, custom pipeline components, and SOAP headers. The first part of my solution involved creating a project holding a property schema. The contained schema element was named TrackingID. It is vital to set the node's Property Schema Base value to MessageContextPropertyBase. This means that the promoted value comes from the message's context instead of the message body itself. Without setting this, the orchestration would gag when trying to use this value for correlation.

The next step was to build a pair of custom pipeline components (using the great pipeline wizard). I'll get to the second component a bit further down. The first component, called TrackingIDPromoter, simply creates a unique ID, and throws it into the promoted context of the message, like so ...

public IBaseMessage Execute(IPipelineContext pc, IBaseMessage inmsg)
{
string trackCode = Convert.ToString(System.Guid.NewGuid());
inmsg.Context.Promote("TrackingID", "http://Microsoft.Demo.Customer.CustomerPropertySchema", trackCode);
return inmsg;
}

Next, I have a BizTalk project holding my orchestration. The orchestration receives a message, sends a message out, and receives that same message back. My message variable is of type System.Xml.XmlDocument, thus untyped and able to hold any valid XML content. I also created a Correlation Type that points to Microsoft.Demo.Customer.TrackingID as the correlated property. And, I can also access this unique tracking value in my message context by doing this ...

InboundMessage(Microsoft.Demo.Customer.TrackingID);
My customer can then take this value, and set it to the MSMQ label so that it can be extracted later.

Now, I want to take the port used to get the message back to my orchestration and expose it as a web service. But, I need a way to also be able to pass that tracking code back in. So, I decided to create a SOAP header to store it. So, I built a simple XSD schema with a root name WSHeader and containing a single element called TrackingID. When do I use this? Well, when you walk through the Web Service Publishing Wizard, you select Add additional SOAP headers. When you click Next, you get prompted to add the header. Here you choose an available schema (in my case, the WSHeader) and continue on. Now when you look at the web service definition, you can see the header expected.


Now of course, the second custom pipeline component comes in. This one is used on the SOAP receive location, and takes the value OUT of the SOAP header, and throws it back into my promoted TrackingID value, and thus allowing the message to correlate back. This pipeline component's Execute method looks like this ...

public IBaseMessage Execute(IPipelineContext pc, IBaseMessage inmsg)
{
string headerVal = inmsg.Context.Read("WSHeader", "http://schemas.microsoft.com/BizTalk/2003/SOAPHeader").ToString();

System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
doc.LoadXml(headerVal);
//get value of tracking code from soap header
string trackingID = doc.SelectSingleNode("//TrackingID").InnerText;

//promote value back into context
inmsg.Context.Promote("TrackingID", "TrackingID", "http://Microsoft.Demo.Customer.CustomerPropertySchema", trackingID);
return inmsg;
}
Note that the namespace for the SOAP header was NOT the value in the header schema, but the standard SOAPHeader namespace.

So these custom pipeline components get thrown into a couple receive pipelines in my BizTalk project, and get deployed. Then, I built a silly little helper app that sends an XML message back to this web service. When you create a reference to a web service with a SOAP header, you also get an object for that header. So my code to submit to this service looked like ...

//CustService is the name of my web service reference CustService.Microsoft_Demo_Customer_Orchestration1_Port3 svc = new MyService.Microsoft_Demo_Customer_Orchestration1_Port3();
CustService.WSHeader header = new CustService.WSHeader();
//set header value to queue label retrieved
header.TrackingID = queueLabelBox.Text;
svc.WSHeaderValue = header;

svc.Operation_1(xmlInputDoc);
I still love the fact that you get all these nicely typed objects for web services. There's an object named after my SOAP header, and it has a property I need. Great stuff.

So, now when I receive a message, my pipeline immediately stamps it with a unique ID (in the message context, not body). When I send the message out, I initialize a correlation set using the promoted value. On the way back in, I throw that value in the SOAP header, which my next pipeline yanks out and sticks back into context, thus enabling full correlation, while not once mucking with (or even knowing the structure of) my base message. Neat huh?

Technorati Tags: