Handcrafting WCF-friendly WSDLs
It's 10:15 pm and I'm still in the office, but I really think I should get this entry posted before I head home - or it won't ever get posted. I was supposed to be working on my demo for my Service Factory session at the SOA Conference tomorrow, but I got side-tracked early in the day when I started playing with a new feature of Service Factory (that I'm going to demo). This new recipe (hereforth referred to as "the new recipe") allows you to create the service interface, data contracts, and a stub of the service implementation from an existing WSDL document. Yeah, pretty cool, huh? Of course you can do the same thing in svcutil if you have the .NET 3.0 SDK installed, but you have to leave Visual Studio and head off to the command prompt - and it won't separate the parts into multiple files and place them in the appropriate projects.
As I'm sure you've already realized, we've added this feature for the masses who have adopted a contract-first approach to building services. Unfortunately for these masses, the object model you have to code against after you've generated code from your existing WSDLs (using either svcutil or the new recipe) is not as enjoyable as what you get with the code-first approach WCF and its DataContract offer. I'm hoping this entry will add some clarity for those of you who wish to have the best of both worlds: Contract-First and DataContracts. First, let's talk about the DataContract.
The DataContract
Rather than start from ground zero with DataContract basics and the DataContractSerializer, I'm going to recommend you go read Aaron Skonnard's excellent Service Station article on Serialization in WCF before continuing. Okay, now that you know DataContract only supports a subset of XML Schema, let's get more specific about this "subset". I am in no way stating that this is a complete list, but these are the things I've personally been able to verify. If you use any of the following constructs, svcutil and the new recipe will fall back to creating XML serializable types (like the ones in ASMX) instead of DataContracts.
- <xs:element ref= ...
- <xs:anyAttribute ...
- <xs:group ref= ...
- <xs:attribute ...
- <xs:choice ...
- <xs:any ...
- <xs:all ...
Of course this pretty much means to be safe, you need to stick with a <xs:sequence> of <xs:elements>. However, I was a little surprised to see that a <xs:simpleType> <xs:restriction> on a xs:string generated a very nice enum. Oh, News Flash! Kirill just responded to my email (hey, what's he doing on email this late?) and pointed me to some [internal] beta documentation that very clearly defines how all XSD constructs map to DataContracts. The doc looks awesome - I'll be sure to update this entry once it is live on the Web (if I don't, remind me). Okay, but you're not in the clear yet. You see, now we have to navigate the message wrapping issue.
The MessageContract
Because the DataContract is all about simplifying interop and the object model (OM), it hides wrapping element from the code, but (obviously) not from the WSDL. So, if you're going to import your handcrafted WSDLs, you need to know how to represent the wrapper "message" in WSDL in such a way that you don't have to deal with them in your OM. I think the easiest way to explain this part would be to start with a fragment of a WSDL and show what must change to get the right stuff in code. Consider this:
<wsdl:definitions xmlns:wsdl="https://schemas.xmlsoap.org/wsdl/"
xmlns:xs="https://www.w3.org/2001/XMLSchema"
xmlns:srvc="https://MyOrg.HrSrvc.SrvcContracts/2006/10"
xmlns:data="https://MyOrg.HrSrvc.DataContracts/2006/10"
targetNamespace="https://MyOrg.HrSrvc.SrvcContracts/2006/10">
<wsdl:types>
<xs:schema elementFormDefault="qualified"
targetNamespace="https://MyOrg.HrSrvc.DataContracts/2006/10"
xmlns:tns="https://MyOrg.HrSrvc.DataContracts/2006/10">
<xs:element name="Employee" type="tns:Employee"/>
<xs:complexType name="Employee">
<xs:sequence>
<xs:element name="ID" type="xs:int"/>
<xs:element name="Name" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
</wsdl:types>
<wsdl:message name="AddEmployeeSoapIn">
<wsdl:part name="request" element="data:Employee" />
</wsdl:message>
<wsdl:message name="AddEmployeeSoapOut">
<wsdl:part name="response" element="data:Employee" />
</wsdl:message>
<wsdl:portType name="IEmployeeManager">
<wsdl:operation name="AddEmployee">
<wsdl:input message="srvc:AddEmployeeSoapIn" />
<wsdl:output message="srvc:AddEmployeeSoapOut" />
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="YouGetTheIdea"/>
</wsdl:definitions>
Yes, I know I'm sending and receiving the same type. I'm trying to balance between brevity and reality (cut me some slack). Hopefully at this point I have some contract-first readers nodding their head thinking, "Ok, I'll buy that - seems reasonable enough." Now let's look at how we would have to write this to get the nice dev experience with WCF ('cause this won't create it).
<wsdl:definitions xmlns:wsdl="https://schemas.xmlsoap.org/wsdl/"
xmlns:xs="https://www.w3.org/2001/XMLSchema"
xmlns:srvc="https://MyOrg.HrSrvc.SrvcContracts/2006/10"
xmlns:data="https://MyOrg.HrSrvc.DataContracts/2006/10"
targetNamespace="https://MyOrg.HrSrvc.SrvcContracts/2006/10">
<wsdl:types>
<xs:schema elementFormDefault="qualified"
targetNamespace="https://MyOrg.HrSrvc.DataContracts/2006/10"
xmlns:tns="https://MyOrg.HrSrvc.DataContracts/2006/10">
<xs:element name="Employee" type="tns:Employee"/>
<xs:complexType name="Employee">
<xs:sequence>
<xs:element name="ID" type="xs:int" nillable="true"/>
<xs:element name="Name" type="xs:string" nillable="true"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
<xs:schema elementFormDefault="qualified"
targetNamespace="https://MyOrg.HrSrvc.SrvcContracts/2006/10"
xmlns:data="https://MyOrg.HrSrvc.DataContracts/2006/10"
xmlns:tns="https://MyOrg.HrSrvc.SrvcContracts/2006/10">
<xs:import namespace="https://MyOrg.HrSrvc.DataContracts/2006/10"/>
<xs:element name="AddEmployee"/>
<xs:complexType>
<xs:sequence>
<xs:element name="request" type="data:Employee"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="AddEmployeeResponse"/>
<xs:complexType>
<xs:sequence>
<xs:element name="AddEmployeeResult" type="data:Employee"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
</wsdl:types>
<wsdl:message name="AddEmployeeSoapIn">
<wsdl:part name="parameters" element="srvc:AddEmployee" />
</wsdl:message>
<wsdl:message name="AddEmployeeSoapOut">
<wsdl:part name="parameters" element="srvc:AddEmployeeResponse" />
</wsdl:message>
<wsdl:portType name="IEmployeeManager">
<wsdl:operation name="AddEmployee">
<wsdl:input message="srvc:AddEmployeeSoapIn" />
<wsdl:output message="srvc:AddEmployeeSoapOut" />
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="YouGetTheIdea"/>
</wsdl:definitions> prefix="o" ?>
The highlighted areas are the parts I added/changed from the first snippet. It’s after midnight now. I’ve learned the beta of Windows Live Writer I was using has a length limitation and doesn’t color XML snippets (yes, I did this manually for your reading pleasure). So rather than continue to wow you with my use of compelling prose, I’ll just bulletize (see?) the significant parts: namespace="" ns="urn:schemas-microsoft-com:office:office" prefix="o" ?>
· What’s up with the nillable=”true” on the data types? That actually turns out to be pretty critical. I haven’t really thought to much about why yet, but without this construct, it complicates the OM bigtime.
· What about that new schema … doesn’t that complicate the OM? Surprisingly not! It won’t even show up in the generated code. Notice how it imports the existing schema to reference the existing types, which do show up in the code. If you don’t explicitly create this wrapper, it will think the first element it finds (Employee otherwise) is the wrapper … yuck.
· Can the wrapper elements (AddEmployee & AddEmployeeResponse) be in the same namespace as the data type (Employee)? Well, that depends. They can if that namespace just happens to be the same as the service. In other words, the wrapper schema has to share the same namespace as the service. It doesn’t matter if the data types are in the same namespace or not.
· Don, is the fact you named the wrapper elements “AddEmployee” and “AddEmployeeResponse” important? Yes. If you don’t follow this convention, you’ll see these as types in the code … yuck!
· Let me guess, it’s also important that I name the data:Employee elements “request” and “AddEmployeeResult” respectively? Well, not really so much. If you name them something else, like “RequestMessage” and “ResponseMessage,” the System.ServiceModel.MessageParameterAttribute will step in to make sure the response message is correct on the wire, but it won’t affect the OM so much. It will look like this:
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
[ServiceContract(Namespace="https://MyOrg.HrSrvc.SrvcContracts/2006/10"]
public interface IEmployeeManager
{
[OperationContract(Action="...", ReplyAction="*")]
[return: MessageParameter(Name="ResponseMessage")]
Employee AddEmployee(Employee RequestMessage);
}
· Alright, what’s up with the “parameter” message part names? Yeah, that’s important too. That basically says this contract is wrapped and not bare.
I think that’s about it. I’ll update this entry if I think (or learn) of anything else, but I’m going home now. Hope you find this helpful. BTW, I was listening to the Lemonheads the entire time I was putting this together … pretty cool.
Comments
Anonymous
October 07, 2006
PingBack from http://blog.robinzhong.com/index.php/archives/2006/10/08/134.htmlAnonymous
October 08, 2006
wouldn't it be nice if the well known set of XSD constructs known to work with WCF were submitted to the W3C XML Schema Patterns for Databinding?Anonymous
October 09, 2006
>I’ve learned the beta of Windows Live Writer >I was using has a length limitation and >doesn’t color XML snippets What is the length limitation you encountered? Also, while Writer doesn't natively support colorizing your XML snippets, there are several "Insert Code" plugins out there that will add this feature for you. It looks like you are pasting from Visual Studio, so the PasteSourceAsHTML would allow you to paste the formatted clipboard data from VS directly into your blog post. http://wlwplugins.com/vb-pastesourceashtml.php -SpikeAnonymous
October 10, 2006
Hi John, Your post is now linked from the <a href="http://www.netfxguide.com/guide/wcf.aspx">WCF Section</a> of NetFXGuide.com. Best, Francesco NetFXGuide.comAnonymous
October 12, 2006
Hey Don. You are entering the scary world of interoperability. I applaud you. Code first in an M$FT only world makes life dead easy. Contract first in a heterogenorous environment nearly always leads to pain. I guess this is the reason why Christian's WS-Contract First tool didn't get embedded in VS2005, even thought there were lots of DCRs requesting it. Might I suggest that you start by making any contract first tooling initially only work with WSDL that is WSI Basic Profile compliant. Which, in turn, means you need a tool for checking this compliance (and that would be a real valuable recipe inside the guidance toolkit) You and I both know that WSDL generated by different toolkits is rarely interoperable (try JDeveloper...). So start with what is easy. Move up the value chain. Ping me if you want to talk this through. See you in Barcelona!Anonymous
October 26, 2006
Data Contract Schema Reference: http://windowssdk.msdn.microsoft.com/en-us/library/ms733112.aspxAnonymous
February 13, 2007
Consider yourself reminded -- "I'll be sure to update this entry once it is live on the Web (if I don't, remind me)."Anonymous
February 14, 2007
Alex already did it for me (the comment above yours). It's a great doc ... really removes all ambiguity. Thanks for reminding me.Anonymous
February 18, 2007
Hi Don, I'm having trouble with the new recipe. I was hoping it would allow me to generate all the necessary classes from a single text file containing the WSDL. However, it prompts me for a web service address - does there already have to be an existing service for this to work? I tried putting in the address as ///file/C:/blahblah , which crashed VS. Then I tried creating a virtual directory from which I could access the file - this works when I point it at a file containing WSDL pulled off an existing service... however, if I change that WSDL - even to change the target namespace - I get an error that the wsdl is not properly formed or does not have any valid contract. Where am I going wrong? BTW (in case it's relevant or helps someone else) in order to get this far, I had to modify the solution file manually and add in a GlobalSection(ExtensibilityGlobals) = postSolution section, containing a ServiceContractNamespace and IsWCSFSolution = trueAnonymous
February 19, 2007
Many apologies, it was a problem with my wsdl.Anonymous
April 07, 2007
Don, I'm attempting to convert an xsd into a Data Contract compliant schema. The source xsd has a <xs:choice> which is forbidden in the Data Contract schema. I have the hardest time looking for an alternative, could you please steer me to the right directions?? Thank You, DougAnonymous
May 14, 2007
Doug, an alternative to choice (which is often a problem or awkward for code generation) would be to use polymorphism (xsd extension). The base can also be abstract.Anonymous
April 25, 2008
WSDL-first (Contract-first) Web Service development with Visual Studio 2008