EAI Guy.net

Enterprise Applicaiton Integration and SOA 2.0

Tag Archives: WCF

BizTalk 2010 – Converting WSE Send Port to WCF

This post outlines my efforts to upgrade a BizTalk application that calls a deprecated Web Service Enhancements (WSE) web service.

Context

This post covers creating a BizTalk 2010 WCF send port and required pipeline components to call a WSE web service. In my scenario, I used a two-way WCF-Basic send port subscribing to messages coming from a receive location. The response returned from the WSE web service contained an escaped XML payload, so I wrote a custom pipeline component (perhaps fodder for a future blog post) to un-escape the XML. The response also contained a success/error status field, which I promoted. I created a send port subscribing to the Success status, and an orchestration subscribing to the Error status for generating ESB fault messages.

Step 1: Capture a WSE HTTP Request

My first step in replicating WSE send-port functionality with WCF was to try capturing an HTTP Post from my BTS 2006 WSE send port.

  • I first tried using Fiddler to intercept the request, which required me to set the WSE port’s proxy to http://127.0.0.1:8888/. However, this proxy address caused BizTalk to suspend the message with this error: Invalid URI: The hostname could not be parsed.
  • I then turned to Wireshark with better luck. I removed the Fiddler proxy settings, started a capture, kicked off a message, and saw the HTTP Post show up in Wireshark. Right-clicking the record and selecting Follow TCP Stream converted the stream to readable text:Image
  • The HTTP post body is displayed below; this is the SOAP format I needed to replicate with WCF.
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Header>
    <wsa:Action>http://myaction.com/webservices/MyAction</wsa:Action>
    <wsa:MessageID>uuid:5dcdf45c-bee9-482a-bfbe-4d189d5f2a6b</wsa:MessageID>
    <wsa:ReplyTo>
      <wsa:Address>http://schemas.xmlsoap.org/ws/2004/03/addressing/role/anonymous</wsa:Address>
    </wsa:ReplyTo>
    <wsa:To>http://mysite.com/myfolder/webservices/myservice.asmx</wsa:To>
    <wsse:Security soap:mustUnderstand="1">
      <wsu:Timestamp wsu:Id="Timestamp-45641c38-06a4-4b44-b382-6d35471e275f">
        <wsu:Created>2013-07-01T21:39:26Z</wsu:Created>
        <wsu:Expires>2013-07-01T21:44:26Z</wsu:Expires>
      </wsu:Timestamp>
      <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="SecurityToken-a4c6efe3-4d17-47f8-8d1a-d3239fe3cdcf">
        <wsse:Username>myUserName</wsse:Username>
        <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">MyPassword</wsse:Password>
        <wsse:Nonce>VOgPryrIoe8saLDE8wB6vg==</wsse:Nonce>
        <wsu:Created>2013-07-01T21:39:26Z</wsu:Created>
      </wsse:UsernameToken>
    </wsse:Security>
  </soap:Header>
  <soap:Body>
    <ns0:Export xmlns:ns0="http://mySite.com/webservices/">
      <ns0:xmlInputDocument>&lt;?xml version="1.0" encoding="utf-8" ?&gt;&lt;moreStuff /&gt;</ns0:xmlInputDocument>
    </ns0:Export>
  </soap:Body>
</soap:Envelope>

Step 2: Create WCF Schema and Bindings and Test

I then generated schemas and bindings, and tested hitting the WSE web service with a WCF adapter to see how far off from the above format I was.

  1. I used BizTalk’s “Add Generated Items” feature to kick off the WCF Service Consuming Wizard, which generated the a schema and a bindings file.
  2. After deploying the application, I imported the generated bindings file to create a WCF-BasicHttp send port.
  3. I set the port to use a proxy address of http://127.0.0.1:8888/ and so I could inspect the HTTP Post and response with Fiddler.
  4. I then setup various receive and send ports to test the WCF Send Port and kicked off a message.
  5. The result was the following error returned from the WSE web service: Unexpected System Exception:#NullReferenceException – Object reference not set to an instance of an object. received – contact Application manager.
  6.  Fiddler showed that the SOAP request generated by my WCF port was missing a <header> element:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
(no <header> element here)
  <s:Body>
    <ns0:Export xmlns:ns0="http://mysite.com/webservices/">
      <ns0:xmlInputDocument>&lt;?xml version="1.0" encoding="utf-8" ?&gt;&lt;moreStuff /&gt;</ns0:xmlInputDocument>
    </ns0:Export>
  </s:Body>
</s:Envelope>

Step 3: Generate SOAP Headers

The next step was to generate the SOAP headers, then get the WCF adapter to include the headers in the request it generates.

  1. I learned from Mikael Sand’s blog post on Setting custom SOAP headers in the WCF adapter that I needed to create a OutboundCustomHeaders context property and populate it with my SOAP header information so the WCF adapter would include my headers in its request. I accomplished this via a custom Assemble pipeline component, as described in the next section.
  2. A closer review of the SOAP header revealed a mix of static and dynamic content, as highlighted below in blue and red, respectively. The next section outlines my solution. I found Ben Powel’s blog post helpful in generating the WSE UsernameToken element.

HighlightedHeader

Step 4: Custom Assemble Pipeline Component

I created a custom “assemble” pipeline component for generating SOAP headers and including them in the message context.

  • I created a custom pipeline component using the wizard.
  • I gave the pipeline component properties for Username and Password, and a property called AdditionalHeaderElementText to hold static content (blue above), to allow flexibility. I set this property to the following:
<wsa:Action>http://mysite.com/webservices/Export</wsa:Action><wsa:ReplyTo><wsa:Address>http://schemas.xmlsoap.org/ws/2004/03/addressing/role/anonymous</wsa:Address></wsa:ReplyTo><wsa:To>http://mysite.com/mywebservices/webservices/readingmanager.asmx</wsa:To>
  • Note: If you set component values when creating a pipeline, then export bindings from the admin console, the pre-set values do not show in bindings. To avoid confusion, I recommend leaving property values blank when creating a pipeline, and manually setting them in the admin console. 
  • Here is my Execute method, which successfully generated the SOAP header for inclusion by the WSF adapter in the HTTP payload:
// http://www.microsoft.com/downloads/en/details.aspx?FamilyID=018a09fd-3a74-43c5-8ec1-8d789091255d&displaylang=en
using Microsoft.Web.Services3.Security.Tokens;

public Microsoft.BizTalk.Message.Interop.IBaseMessage Execute(Microsoft.BizTalk.Component.Interop.IPipelineContext pc, Microsoft.BizTalk.Message.Interop.IBaseMessage inmsg)
{
  string namespaces = "xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" " +
        "xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/03/addressing\" " +
        "xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\" " +
        "xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\"";

  string timestamp = String.Format("<wsu:Timestamp wsu:Id=\"Timestamp-{0}\">"
    + "<wsu:Created>{1}</wsu:Created><wsu:Expires>{2}</wsu:Expires></wsu:Timestamp>",
    Guid.NewGuid(), DateTime.UtcNow.ToString("s") + "Z", DateTime.UtcNow.AddMinutes(5).ToString("s") + "Z");

  var token = new UsernameToken(this.Username, this.Password, PasswordOption.SendPlainText); // http://blog.benpowell.co.uk/2010/11/supporting-ws-i-basic-profile-password.html

  string usernameToken = token.GetXml(new XmlDocument()).OuterXml;
  // remove namespace definitions since we'll define them in the <header> node below
  usernameToken = Regex.Replace(usernameToken, " xmlns:[^=]+=\"[^\"]+\"", "");

  string securityXml = "<wsse:Security s:mustUnderstand=\"1\">" + timestamp + usernameToken + "</wsse:Security>";

  string header = string.Empty;
  // add header fields from config
  header += this.AdditionalHeaderElementText;                 
header += "<wsa:MessageID>uuid:" + inmsg.MessageID + "</wsa:MessageID>"; 
  header += securityXml;
  header = String.Format("<headers {0}>{1}</headers>", namespaces, header);

  // Write the OutboundCustomHeaders property to the message context (distinguish it) so the WCF adapter will add these values to the SOAP header
  // http://blogical.se/blogs/mikael_sand/archive/2012/07/06/setting-custom-soap-headers-in-the-wcf-adapter.aspx     
  inmsg.Context.Write("OutboundCustomHeaders", "http://schemas.microsoft.com/BizTalk/2006/01/Adapters/WCF-properties", header);

  return inmsg;
}
  • Note: I first tried promoting OutboundCustomHeaders property, but I ran into the “The property ‘propertyname’ has a value with length greater than 256 characters” error. Distinguishing the field instead of promoting it resolved the error. 

Summary

It would be ideal to upgrade WSE web services to WCF to improve performance. If, however, you are stuck calling a WSE web service and you want to use a BizTalk WCF send port, I hope this post helps. Please respond with any questions or comments.