« Amazon Marketplace Web Service (MWS) BizTalk WCF Adapter | Main | Automating the BizTalk WCF Service Publishing Wizard »
Monday
Jun252012

Contract First Development and BizTalk Server

Got asked to setup a demo to show how BizTalk handles contract first development within a service orientated architecture. If BizTalk consumes a web service there isn’t really much to talk about, it’s easy enough provided the published contract or WSDL adheres to a couple of minor considerations, granted they’re not so minor if you bump into one.

The client was looking to define WSDL files using a tool like Eclipse or SOAPUI which they could then distribute so all relevant parties could get coding. The BizTalk developers would take that file and build a service that conforms to the contract. If you’re familiar with BizTalk you could probably start to see the challenge. There’s a chapter in Richard Seroter’ book SOA Patterns with BizTalk 2009 which has a section on one way to do this, for this post I’ve used his approach on exposing the WSDL but decided do something different regarding the schemas.

When you generate a WCF service rather than choose all the schemas you want to expose it’s possible to just select the “Any” message type from the Microsoft.XLANGs.BaseTypes.dll assembly. This basically means your service will accept messages and not care about the type. This enables BizTalk to receive the messages defined in the contract without having to worry about getting the schemas to match in the service. You’ll still have message type validation when the message is published into BizTalk so the client could not send just any old message without getting an exception. What you would lose is the field level validation that a strongly typed service would provide, but you could get the similar behavior by adding a validation pipeline component to the receive location. All we’re really doing is delaying the message validation until it hits BizTalk when a traditional service would do that in IIS.

To the client the only difference would be the metadata endpoint as the messages would be of type Any rather than Order or OrderResponse. Again this isn’t a big deal as the client is using the contract. Having said that the workaround from SOA Patterns enabled BizTalk WCF Service to use our handcrafted WSDL rather than generate one, so really our BizTalk service will behave and look just like it should.

The requirements

The requirements for the demo were pretty simple. I had to use the distributed WSDL to define types in BizTalk and they wanted BizTalk to publish the WSDL for discovery so the metadata endpoint had to match the contract.

The Contract

Obviously I needed a contract. Given they were going to use Eclipse I figured that’s a good place to start. One of the concerns I had were those considerations mentioned earlier when BizTalk consumes a web service. Using the same tool the client will use to craft the WSDL would ensure there aren’t as many gotchas for the client later on. Couple of downloads, a 64-bit java issue and a few clicks later I had my contract. Just a basic PlaceOrder method that took in an Order request and returned OrderResponse.

image

 

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://www.demo.org/ContractFirst/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
name="ContractFirst" targetNamespace="http://www.demo.org/ContractFirst/">
<wsdl:types>
<xsd:schema targetNamespace="http://www.demo.org/ContractFirst/">
<xsd:element name="Order" type="tns:Order">
</xsd:element>
<xsd:element name="OrderResponse" type="tns:OrderResponse">
</xsd:element>
<xsd:complexType name="Order">
<xsd:sequence>
<xsd:element name="PONumber" type="xsd:int"></xsd:element>
<xsd:element name="CustomerID" type="xsd:string"></xsd:element>


<xsd:element name="OrderDate" type="xsd:date"></xsd:element>
<xsd:element name="Lines" type="tns:Line" maxOccurs="unbounded"
minOccurs="1">
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="OrderResponse">

<xsd:sequence>
<xsd:element name="Status">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="Success"></xsd:enumeration>
<xsd:enumeration value="Failed"></xsd:enumeration>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
<xsd:element name="OrderNumber" type="xsd:string"></xsd:element>
<xsd:element name="Message" type="xsd:string"></xsd:element>
</xsd:sequence>
</xsd:complexType>

<xsd:complexType name="Line">
<xsd:sequence>
<xsd:element name="ItemNumber" type="xsd:int"></xsd:element>
<xsd:element name="Quantity" type="xsd:int"></xsd:element>
<xsd:element name="Price" type="xsd:double"></xsd:element>
</xsd:sequence>
<xsd:attribute name="LineNumber" type="xsd:string"></xsd:attribute>
</xsd:complexType>
</xsd:schema>
</wsdl:types>
<wsdl:message name="PlaceOrderRequest">
<wsdl:part name="part" element="tns:Order" />
</wsdl:message>
<wsdl:message name="PlaceOrderResponse">
<wsdl:part name="part" element="tns:OrderResponse" />
</wsdl:message>
<wsdl:portType name="ContractFirst">
<wsdl:operation name="PlaceOrder">
<wsdl:input message="tns:PlaceOrderRequest"/>
<wsdl:output message="tns:PlaceOrderResponse"/>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="ContractFirstSOAP" type="tns:ContractFirst">

<soap:binding style="document"
transport="http://schemas.xmlsoap.org/soap/http" />
<wsdl:operation name="PlaceOrder">

<soap:operation
soapAction="http://www.demo.org/ContractFirst/PlaceOrder" />
<wsdl:input>

<soap:body use="literal" />
</wsdl:input>
<wsdl:output>

<soap:body use="literal" />
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="ContractFirst">
<wsdl:port binding="tns:ContractFirstSOAP" name="ContractFirstSOAP">
<soap:address location="http://www.demo.org/"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>

I saved the WSDL to common folder, contract done!

The Client

Next I created a test harness or client that will eventually be able to call BizTalk and submit an Order. For the client everything I need to know at this point is contained in the contract even though the service doesn’t exist. A simple console application will work, just have to add a service reference to the WSDL file I saved earlier.

image

The code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TestHarness
{
class Program
{
static void Main(string[] args)
{
try
{
ServiceReference1.ContractFirstClient client =
new ServiceReference1.ContractFirstClient();

ServiceReference1.Line line1 = new ServiceReference1.Line{
ItemNumber = 1234,
LineNumber ="1",
Price = 100,
Quantity = 10};

ServiceReference1.Line line2 = new ServiceReference1.Line{
ItemNumber = 1234,
LineNumber ="2",
Price = 100,
Quantity = 10};

ServiceReference1.Order order = new ServiceReference1.Order
{
CustomerID = "12345",
OrderDate = DateTime.Now,
PONumber = 998877,
Lines = new[] { line1, line2 }
};

Console.WriteLine("Sending PlaceOrder...");

ServiceReference1.OrderResponse response = client.PlaceOrder(order);

Console.WriteLine("Received response: {0}", response.Status);
Console.WriteLine("Order Number: {0}", response.OrderNumber);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
finally
{
Console.Read();
}
}
}
}

The app.config that will get created is good to go, you’ll just have to change the endpoint address when the BizTalk service is finally published.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="ContractFirstSOAP" closeTimeout="00:01:00" openTimeout="00:01:00"
receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false"
bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<security mode="None">
<transport clientCredentialType="None" proxyCredentialType="None"
realm="" />
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://www.demo.org/contractfirst" binding="basicHttpBinding"
bindingConfiguration="ContractFirstSOAP" contract="ServiceReference1.ContractFirst"
name="ContractFirstSOAP" />
</client>
</system.serviceModel>
</configuration>

The Service

Everything up until this point has been fairly straightforward as in no workarounds or pitfalls, now we need to create the service. I created a simple orchestration that’s going to subscribe to the Order message, construct an OrderResponse and send it back out. 

image

 

Frist we’ll have to add a Web Reference to bring in my contract.

image

 

This will create all my message types and ports. They’ll be the web variety but that’s okay.

image

 

Back in the orchestration we want to add a port that will receive the Order message and send out the OrderResponse. First issue. The Web PortTypes have their direction predefined, as in they can only send a request and receive a response. This is fine for most instances when you add a web reference but this won’t work in this scenario as we want to receive the request and send a response even though we’re referencing a WSDL.

image

 

It’s probably possible to get into the Reference.odx that’s generated by adding a web reference and change it but I wouldn’t advise that. You’ll have to change it every time you update the contract. The workaround is to just define your own port types and use the web message types from the web reference. This way you can control the direction and still use the types as defined in the contract.

image

 

Next we’ll create the actual SOAP endpoint that our client will call. To do this you’ll have to deploy the project and open the WCF Publishing Wizard. On the first screen make sure you choose WCF-CustomIsolated as the adapter. CustomIsolated will enable us to use a custom or external metadata file via the servieMetadata behavior.

image

 

Publish Schemas.

image

 

Here we get to the next workaround.

image

 

On this screen we’re expected to select the schemas from our project. We could in theory select the web message types that get generated by the web reference but I suspect that’ll get nasty. As explained at the top of the post I decided to use the Any message types to create generic service. It still isn’t really generic as BizTalk will still validate the message types so a client could not send an unknown message type without getting an exception. Having said that if you don’t add a validation pipeline component it would be possible to receive a message that doesn’t conform to the contract as BizTalk does not validate against the schema by default.

image   

   image

 

Now you can finish the wizard and create the service. You’ll probably have to add the basicHttpBinding to the receive location but you can now bind your orchestration and start everything up. Change the Testharness app.config to have the correct endpoint address and an Order should get submitted.

image

Metadata Endpoint

The final step is to get the WSDL or contract to be discoverable from the BizTalk Service URL. If you instructed the WCF publishing wizard to create a mex endpoint it will currently look something like this.

image

This is clearly a lot different to the contract we developed in the beginning, notice the xsd:anyType part definitions. There is workaround! First, copy the WSDL file to the virtual directory of your BizTalk WCF Service and note the URL i.e. http://localhost/DemoService/ContractFirst.wsdl it can also be a relative path. Open up the WCF-CustomIsolated receive location and add a serviceMetadata service behavior, set httpGetEnabled to true and enter the URL in the externalMetadataLocation property . 

image

 

This will override the metadata endpoint of the service and add a link to the contract. Now we have a service that not only accepts the types defined in the contract but it’s also discoverable. As the contract changes you don’t even need to change your WCF service just update the web reference in the BizTalk project and copy the contract to the virtual directory.

The code needs to be scrubbed of any client names so I’ll have to add that later.

Reader Comments (2)

The BizTalk developers would take that file and build a service that conforms to the contract. If you’re familiar with BizTalk you could probably start to see the challenge.

October 23, 2012 | Unregistered CommenterMyra Keller

The only challenge relates to how BizTalk generates Web Port Types from WSDL contracts. The example above shows how to work around that.

October 23, 2012 | Registered CommenterMichael Lipscombe

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>