Using WS-Addressing in BizTalk Server 2004

In the past day I developed some tests using the BizTalk Adapter for WSE 2.0. This adapter makes possible to develop powerful BizTalk-based applications that takes advantages of some of the most important WS-* protocols. The adapter put emphasis in specifications like WS-Security, WS-Policy but also provides support for other specifications like WS-Addressing and WS-Referrals. In this post I want o show a little code that explains how to combines WS-Addressing and BizTalk dynamic ports to build and interesting solution.

WS-Addressing is one of the most important WS-* specifications that provides a transport neutral mechanism to addressing Web Services. This specification is at the same time simple and powerful and other specifications like WS-Eventing relies in WS-Addressing to identify the Web Services endpoints.

The BizTalk adapter for WSE has a WSEReceiver and WSETransmitter adapters. As part of its process both adapters maps SoapEnvelopes in IBaseMessage interfaces and vice versa. If we gain access to interfaces like IBaseMessage and IBaseMessageFactory we can use Microsoft.BizTalk.Adapter.Wse.Helper.MessageConverter class to make the message transformations. This class in turn uses the Microsoft.BizTalk.Adapter.Wse. Helper.BiztalkMessageGenerator and Microsoft.BizTalk.Adapter.Wse. Helper.SoapMessageGenerator classes. In the case of the receive adapter some of the headers of the incoming Soap message are mapped into context’s properties (instances of classes that inherit from MessageContextPropertyBase; the Microsoft.BizTalk.Adapter.WSE.Properties assembly contains the definition of these classes). We can access to context properties in orchestrations, port’s filters and pipeline components.

Now suppose that we have an orchestration published as a SoapService using the WSE publishing wizard that comes as part of the WSE adapter installation package. My orchestration receives a SOAP messagve, do some work and invokes an operation of a Web Service which location is unknown until runtime. This is a typical scenario for dynamic ports. The location of the invoked Web Service comes as part of a WS-Addressing ReplyTo header in the SOAP message which also contains a property that must be passed as a parameter to the invoked operation. Suppose that my orchestration receives a SOAP message with the following header’s section.

  <soap:Header>

     <wsa:MessageID>

          uuid:6B29FC40-CA47-1067-B334-00DD010662DA

     </wsa:MessageID>

     <wsa:ReplyTo>

       <wsa:Address>wse://http://localhost/HttpHost/SimpleService.ashx</wsa:Address>

        <wsa:ReferenceProperties xmlns:c="http://myexample.org/targetservice">

                 <c:MyProp>123456</c:MyProp>

         </wsa:ReferenceProperties>

     </wsa:ReplyTo>     

     <wsa:Action>

           http://tempuri.org/SimpleCosumer_Orchestration_1_InPort/MainOp

   </wsa:Action>       <wsa:To>http://tempuri.org/SimpleCosumer_Orchestration_1_InPort/MainOp</wsa:To>

  </soap:Header>

When the target orchestration receives this message it must extract the value of the wsa:Address and assign it to the send port location. It must also obtain the MyProp value to build the input message to the invoked Web Service. An abstract view of my orchestration looks like this:

…Obtain the WS-Addressing headers…

…Obtain the WS-Address header’s value…

…Obtain the value of MyProp…

..Sets the Web Service address to the dynamic send port…

…Invokes the EchoLog operation of the Web Service interfaced by the dynamic send port..

Obtain the WS-Addressing headers.

A string representation of all the WS-Addressing headers can be obtained via the AddressingHeaders context’s property.

Field1= Mysoapmessage(WSE.AddressingHeaders)

The value of field1 for out input message is the following.

<wsa:MessageID>

          uuid:6B29FC40-CA47-1067-B334-00DD010662DA

     </wsa:MessageID>

     <wsa:ReplyTo>

       <wsa:Address>wse://http://localhost/HttpHost/SimpleService.ashx</wsa:Address>

        <wsa:ReferenceProperties xmlns:c="http://myexample.org/targetservice">

                 <c:MyProp>123456</c:MyProp>

         </wsa:ReferenceProperties>

     </wsa:ReplyTo>     

     <wsa:Action>

           http://tempuri.org/SimpleCosumer_Orchestration_1_InPort/MainOp

   </wsa:Action>       <wsa:To>http://tempuri.org/SimpleCosumer_Orchestration_1_InPort/MainOp</wsa:To>

Obtain the value of WS-Address and MyProp headers.

We have all the addressing headers in a string representation. Now we can uses different variants to obtain the headers value. First of all we can build an Xml document that contains the addressing headers. Next we can use the xpath function to query for the headers value and specify the wsa and c namespaces.

Another variant is to uses a .NET class helper that receives the XML document and obtains the headers value.

To obtain the addressing headers we can uses the classes that come as part of the Microsoft.Web.Services2.Addressing namespace. In the current version of WSE is possible possible to declare an instance of AddressingHeaders directly and use to obtain the headers value. The following pseudo-code shows how to perform this task.

SoapEnvelope MyMessage;

AddressingHeaders h = new AddressingHeaders();

h.Load(MyMessage);

Uri ReplyAddr= h.ReplyTo.Address.Value;

This is a simple variant, there are many other combining some of the classes in the Addressing namespace.

We can also take more control over the XPath queries and uses the XmlNamespaceManager class. The following pseudo-code illustrates this variant:

SoapEnvelope MyMessage;

nsmngr= new XmlNamespaceManager(MyMessage.NameTable);

nsmngr.AddNamespace("wsa", WS-Addressing namespace…);

nsmngr.AddNamespace("c", MyProp namespace…);

XmlElement headers= MyMessage.Header;

String ReplyAddr= headers.SelectSingleNode("//wsa:Address", nsmngr).InnerXml;

string MyPropValue= headers.SelectSingleNode("//wsa:ReplyTo/wsa:ReferenceProperties/c:MyProp", nsmngr).InnerXml;    

 There are, of course, many other solutions to obtain the addressing headers. In any case the solution that you take you should encapsulate it in a helper class and use this class from the BizTalk orchestration.

 

Sets the Web Service address to the dynamic send port and invokes the Web Service operation.

Until now we have the way to obtain the WS-Addressing headers and to query these headers to obtain the address and MyProp headers value. The next thing that we need to do is to assign the obtained address to the dynamic send port and also set the action. We must also use the MyProp property to construct the input message of the target Web Service. We can use the following pseudo-code in a MessageAssigment shape to do these tasks.

//uses the helper class to obtain the ReplyTo address and MyProp property…

ReplyAddr= helper.GetAddress(MyMessage);

MyPropValue= helper.GetPropValue(MyMessage);

MyDyncPort(Microsoft.XLANGs.BaseTypes.Address)= ReplyAddr;

…build the targetmsg input message…

targetMsg(WSE.SoapAction)= MyAction;

This all we need to do; we must set the address of the dynamic send port using the Microsoft.XLANGs.BaseTypes.Address property and next set the SOAP action of the target SOAP message.

I hope that this example results useful. There is no doubt that the BizTalk Adapter for WSE offers a lot of variants to build powerful BizTalk applications. Combining WS-Addressing and dynamic ports in BizTalk may be a great option to consider when we develop BizTalk-WSE applications.

 

 

No Comments