Do you ever do WSDL-first web service development? Regardless of the reason that you do this (e.g. you’re an architectural-purist, your mother didn’t hold you enough), this style of service design typically works fine with BizTalk Server solutions. However, if you decide to build a one-way input service, you’ll encounter an annoying, but understandable error.
Let’s play this scenario out. I’ve hand-built a WSDL that takes in an “employee update” message through a one-way service. That is, no response is needed by the party that invokes the service.
The topmost WSDL node defines some default namespace values and then has a type declaration which describes our schema.
<wsdl:definitions name="EmployeeUpdateService" targetNamespace="http://Seroter.OneWayWsdlTest.EmployeeProcessing" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://Seroter.OneWayWsdlTest.EmployeeProcessing"> <!-- declare types--> <wsdl:types> <xs:schema xmlns="http://Seroter.OneWayWsdlTest.EmployeeProcessing" xmlns:b="http://schemas.microsoft.com/BizTalk/2003" targetNamespace="http://Seroter.OneWayWsdlTest.EmployeeProcessing" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="EmployeeUpdate"> <xs:complexType> <xs:sequence> <xs:element name="EmpId" type="xs:string" /> <xs:element name="UpdateType" type="xs:string" /> <xs:element name="DateUpdated" type="xs:string" /> </xs:sequence> </xs:complexType> </xs:element> </xs:schema> </wsdl:types>
Next, I defined my input message, port type with an operation that accepts that message, and then a binding that uses that port type.
<!-- declare messages--> <wsdl:message name="Request"> <wsdl:part name="part" element="tns:EmployeeUpdate" /> </wsdl:message> <!-- decare port types--> <wsdl:portType name="EmployeeUpdate_PortType"> <wsdl:operation name="PublishEmployeeRequest"> <wsdl:input message="tns:Request" /> </wsdl:operation> </wsdl:portType> <!-- declare binding--> <wsdl:binding name="EmployeeUpdate_Binding" type="tns:EmployeeUpdate_PortType"> <soap:binding transport="http://schemas.xmlsoap.org/soap/http"/> <wsdl:operation name="PublishEmployeeRequest"> <soap:operation soapAction="PublishEmployeeRequest" style="document"/> <wsdl:input> <soap:body use ="literal"/> </wsdl:input> </wsdl:operation> </wsdl:binding>
Finally, I created a service declaration that has an endpoint URL selected.
<!-- declare service--> <wsdl:service name="EmployeeUpdateService"> <wsdl:port binding="tns:EmployeeUpdate_Binding" name="EmployeeUpdatePort"> <soap:address location="http://localhost:8087/EmployeeUpdateService/Service.svc"/> </wsdl:port> </wsdl:service> </wsdl:definitions>
I copied this WSDL to the root of my web server that so that it has a URL that can be referenced later.
Let’s jump into a BizTalk project now. Note that if you design a service this way (WSDL-first), you CAN use the BizTalk WCF Service Consuming Wizard to generate the schemas and orchestration messaging ports for a RECEIVE scenario. We typically use this wizard to build artifacts to consume a service, but this actually works pretty well for building services as well. Anyway, I’m going to take the schema definition from my WSDL and manually create a new XSD file.
This is the only artifact I need to develop. I deployed the BizTalk project and switched to the BizTalk Administration Console where I will build a receive port/location that hosts a WCF endpoint. First though, I created a one-way Send Port which subscribes to my message’s type property and emits the file to disk.
Next I added a new one-way receive port that will host the service. It uses the WCF-Custom adapter so that I can host the service in-process instead of forcing me to physically build a service to reside in IIS.
On the General tab I set the address to the value from the WSDL (http://localhost:8087/EmployeeUpdateService/Service.svc). On the Binding tab I chose the basicHttpBinding. Finally, on the Behavior tab, I added a Service Behavior and selected the serviceMetadata behavior from the list. I set the externalMetadataLocation to the URL of my custom WSDL and flipped the httpGetEnabled value to True.
If everything is configured correctly, the receive location is started, and the BizTalk host is started (and thus, the WCF service host is opened), I can hit the URL of my BizTalk endpoint and see the metadata page.
All that’s left to do is consume this service. Instead of building a custom application that calls this service, I can leverage the WCF Test Client that ships with the .NET Framework. After adding a reference to my BizTalk-hosted service, and invoking the service, two things happened. First, the message is successfully processed by BizTalk and a file is dropped to disk (via my Send Port). But secondly, and most important, my service call resulted in an error:
The one-way operation returned a non-null message with Action=”.
Yowza. While I could technically catch that error in code and just ignore it (since BizTalk processed everything just fine), that’d be pretty lazy. We want to know why this happened! I got this error because a“one way” BizTalk receive location still sends a message back to the caller and my service client wasn’t expecting it. A WSDL file with a true one-way operation results in a WCF client that expects an IsOneWay=true interaction pattern. However, BizTalk doesn’t support true one-way interactions. It supports operations that return no data (e.g. “void”) only. So, by putting a hand-built WSDL that demanded an asynchronous service on a BizTalk receive location that cannot support it, we end up with a mismatch.
How do I fix this? Actually, it’s fairly simple. I returned to my hand-built WSDL and added a new, empty message declaration.
<!-- declare messages--> <wsdl:message name="Request"> <wsdl:part name="part" element="tns:EmployeeUpdate" /> </wsdl:message> <wsdl:message name="Response" />
I then made that message the output value of my operation in both my port type and binding.
<!-- decare port types--> <wsdl:portType name="EmployeeUpdate_PortType"> <wsdl:operation name="PublishEmployeeRequest"> <wsdl:input message="tns:Request" /> <wsdl:output message="tns:Response" /> </wsdl:operation> </wsdl:portType> <!-- declare binding--> <wsdl:binding name="EmployeeUpdate_Binding" type="tns:EmployeeUpdate_PortType"> <soap:binding transport="http://schemas.xmlsoap.org/soap/http"/> <wsdl:operation name="PublishEmployeeRequest"> <soap:operation soapAction="PublishEmployeeRequest" style="document"/> <wsdl:input> <soap:body use ="literal"/> </wsdl:input> <wsdl:output> <soap:body use ="literal"/> </wsdl:output> </wsdl:operation> </wsdl:binding>
After copying the WSDL back to IIS (so that my service’s metadata was up to date), I refreshed the service in the WCF Test Client. I called the service again, and this time, got no error while the file was once again successfully written to disk by the send port.
BizTalk Server, and the .NET Framework in general, have decent, but not great support for WSDL-first development. Therefore, it’s wise to be aware of any gotchas or quirks when going this route.
Hi Richard,
Nice post as usual, I have come across the same thing a few times, and I think that unless you are using the MSMQ binding you have to be a little careful with the one way attribute which is something Ive seen a lot of people not really pay much attention to.
If you arent using MSMQ and send a one way web service call your client only really knows that the data hit IIS but if you get an error which would result in a soap fault the client isnt going to get it and there is no persistance/durability so the message is probably going to be lost.
When you implement the workaround above the trade of is that there is very slightly more latency in the call because the client will wait until the message has passed through the wcf host (probably IIS) and also through the recieve port and is persisted to the message box. So in exchange for this very small change you get the ability to be sure that your message is in biztalk.