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.