WSDL-First development with WCF

A couple weeks ago I mentioned that you could do WSDL-First development with WCF, but I didn't go into detail as to how that would work.  Somebody asked, so I guess I'll describe the specific steps. I want to use a real scenario, so for a WSDL, I will use the WSDL that Microsoft defines for Microsoft Office Research Services.

Did you get that? Microsoft Office defines a WSDL for Research Services. Any Microsoft Office program, Office 2003 or Office 2007, can call out to any service that implements the given Research Service wire contract. Office programs are web services clients. Ok, we're all clear on that, right?

And this it may be counter intuitive for some people. The client application, in this case, Microsoft Office, specifies the on-the-wire contract, the WSDL. Lots of people have a server-centric design perspective, and assume that the server defines the contract. That often makes sense, but not in this case. Because there are so many deployments of Microsoft Office out there, it makes sense for the client to define the contract.

Back to the WSDL. But let me be straight with you: on the website I referenced, I did not actually see a WSDL specifically defined for the Office Research Service.  I'd expect to find it in the Schema Reference, but that reference includes XML Schema, not web services contracts. On the other hand, the Research Service SDK includes sample applications built on .NET and ASMX, and running those you can generate the WSDL that is being used.  So... effectively the WSDL is implicitly published.  I took some liberties with the WSDL available in that way and I came up with my own WSDL, like so:

 <definitions
  xmlns:http="https://schemas.xmlsoap.org/wsdl/http/"
  xmlns:soap="https://schemas.xmlsoap.org/wsdl/soap/"
  xmlns:s="https://www.w3.org/2001/XMLSchema"
  xmlns:s0="urn:Microsoft.Search"
  xmlns:soapenc="https://schemas.xmlsoap.org/soap/encoding/"
  xmlns:wsaw="https://www.w3.org/2006/05/addressing/wsdl"
  targetNamespace="urn:Microsoft.Search"
  xmlns="https://schemas.xmlsoap.org/wsdl/"
>

  <types>
    <s:schema elementFormDefault="qualified" targetNamespace="urn:Microsoft.Search">

      <s:element name="Registration">
        <s:complexType>
          <s:sequence>
            <s:element minOccurs="0" maxOccurs="1" name="registrationXml" type="s:string" />
          </s:sequence>
        </s:complexType>
      </s:element>
      <s:element name="RegistrationResponse">
        <s:complexType>
          <s:sequence>
            <s:element minOccurs="0" maxOccurs="1" name="RegistrationResult" type="s:string" />
          </s:sequence>
        </s:complexType>
      </s:element>

      <s:element name="Query">
        <s:complexType>
          <s:sequence>
            <s:element minOccurs="0" maxOccurs="1" name="queryXml" type="s:string" />
          </s:sequence>
        </s:complexType>
      </s:element>
      <s:element name="QueryResponse">
        <s:complexType>
          <s:sequence>
            <s:element minOccurs="0" maxOccurs="1" name="QueryResult" type="s:string" />
          </s:sequence>
        </s:complexType>
      </s:element>

      <s:element name="Status">
        <s:complexType />
      </s:element>
      <s:element name="StatusResponse">
        <s:complexType>
          <s:sequence>
            <s:element minOccurs="0" maxOccurs="1" name="StatusResult" type="s:string" />
          </s:sequence>
        </s:complexType>
      </s:element>

    </s:schema>
  </types>


  <message name="RegistrationSoapIn">
    <part name="parameters" element="s0:Registration" />
  </message>
  <message name="RegistrationSoapOut">
    <part name="parameters" element="s0:RegistrationResponse" />
  </message>

  <message name="QuerySoapIn">
    <part name="parameters" element="s0:Query" />
  </message>
  <message name="QuerySoapOut">
    <part name="parameters" element="s0:QueryResponse" />
  </message>

  <message name="StatusSoapIn">
    <part name="parameters" element="s0:Status" />
  </message>
  <message name="StatusSoapOut">
    <part name="parameters" element="s0:StatusResponse" />
  </message>

  <portType name="IResearchServiceSoap">
    <operation name="Registration">
      <input  message="s0:RegistrationSoapIn"  wsaw:Action="urn:Microsoft.Search/Registration" />
      <output message="s0:RegistrationSoapOut" wsaw:Action="" />
    </operation>

    <operation name="Query">
      <input  message="s0:QuerySoapIn"  wsaw:Action="urn:Microsoft.Search/Query" />
      <output message="s0:QuerySoapOut" wsaw:Action="" />
    </operation>

    <operation name="Status">
      <input  message="s0:StatusSoapIn"  wsaw:Action="urn:Microsoft.Search/Status" />
      <output message="s0:StatusSoapOut" wsaw:Action="" />
    </operation>

    </operation>

  </portType>

  <binding name="ResearchServiceSoap" type="s0:IResearchServiceSoap">
    <soap:binding transport="https://schemas.xmlsoap.org/soap/http" style="document" />

    <operation name="Registration">
      <soap:operation soapAction="urn:Microsoft.Search/Registration" style="document" />
      <input>
        <soap:body use="literal" />
      </input>
      <output>
        <soap:body use="literal" />
      </output>
    </operation>

    <operation name="Query">
      <soap:operation soapAction="urn:Microsoft.Search/Query" style="document" />
      <input>
        <soap:body use="literal" />
      </input>
      <output>
        <soap:body use="literal" />
      </output>
    </operation>

    <operation name="Status">
      <soap:operation soapAction="urn:Microsoft.Search/Status" style="document" />
      <input>
        <soap:body use="literal" />
      </input>
      <output>
        <soap:body use="literal" />
      </output>
    </operation>


  </binding>

</definitions>

Ok, starting with that WSDL, given to us by Microsoft Office, we now want to build a service that implements that WSDL, and we want to build it in WCF. We start with the svcutil.exe tool, which is shipped with the .NET SDK v3.0 , or later.

Continuing on with the thought from above: most people have a server-centric design perspective when it comes to web services; most people think the server comes first, then you build the clients...The svcutil.exe tool is also guilty of that.  In fact there is documentation that describes how to build a client using the svcutil.exe tool, but as far as I know there is no doc that describes how to create a WCF service from a WSDL file using the svcutil.exe tool. We're going to fix that. Despite the fact that there is no doc for this, it is in fact a supported scenario.  Constructing the service starting from the WSDL works.

Here's how you generate a server-side "stub" and the interface for that WSDL:

c:\netsdk3.0\bin\svcutil.exe /language:C# /out:IResearchService.cs /n:*,Ionic.Samples.Webservices.Sep24 ResearchService.wsdl

On the command line, I specify the output file, the language, and also the WSDL file itself.  If there are external XSD files, you will need to specify those, too. In this case, the entire contract is contained within a single WSDL file.  The last interesting bit is the /n switch, which I use to specify the namespace for all the generated classes. Without this switch the interface and the Data Access Objects are all generated into the default (global::) namespace, which I don't like. 

You can see that I reference the .NET SDK v3.0 directory for the svcutil.exe tool.  WCF first shipped in the .NET SDK v3.0.  Even if you have .NET SDK 3.5 installed, (or Visual Studio 2008), you will still use the svcutil.exe from .NET 3.0.  This is because of the russian-doll model of releases that .NET 2.0, 3.0 and 3.5 are.

Now, this is a command line, which is definitely not a GUI.  But you can teach Visual Studio to do this for you. To do this, you need to go to the Tools menu, and select External Tools.  Then click Add, and specify these settings for the svcutil tool:

Of course you have to specify the relevant path for the .NET SDK V3.0, for your installation.  Regardless whether you run svcutil.exe from an MSbuild file, or from Visual Studio, here's what the generated interface looks like:

     [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
    [System.ServiceModel.ServiceContractAttribute(Namespace="urn:Microsoft.Search",
        ConfigurationName="Ionic.Samples.Webservices.Sep24.IResearchServiceSoap")]
    public interface IResearchServiceSoap
    {
        
        [System.ServiceModel.OperationContractAttribute(Action="urn:Microsoft.Search/Registration",
            ReplyAction="")]
        [System.ServiceModel.XmlSerializerFormatAttribute()]
        string Registration(string registrationXml);
        
        [System.ServiceModel.OperationContractAttribute(Action="urn:Microsoft.Search/Query",
            ReplyAction="")]
        [System.ServiceModel.XmlSerializerFormatAttribute()]
        string Query(string queryXml);
        
        [System.ServiceModel.OperationContractAttribute(Action="urn:Microsoft.Search/Status",
            ReplyAction="")]
        [System.ServiceModel.XmlSerializerFormatAttribute()]
        string Status();
        
    }

 

The Office Research Service interface is a bit of a bummer, because the operations on this service interface just exchange strings. No complex types here.  But trust me, it works the same if you use complex types in the interface.  In that case, the output source file will include a .NET DataContract definition for the various complex types, along with XML namespace settings and so on.  The silly thing is that the Office Research Service does actually send back XML.  But rather than use xsd:anyType to allow any XML, or even a strictly-specified XML element, the office app just specifies a string. In effect, the XML is encoded as a string.  When implementing the service, you would actually instantiate an XmlDocument and load in the string you get passed, if you know what I mean.  This is normally something the web services runtime would do for you, if you specify your WSDL that way.  But Office did not do that. 

Ok, moving on.... Now, If you are sharp-eyed, you will notice that the command line to generate the server-side stub and interface is the same command line you would use for the Client proxy.  And you're right. In fact if you look in the generated file you will find some client-specific proxy classes. No problem with any of that. For the client we need those proxy classes, but for the server we don't. In a server-specific project, that generated code will remain unused, or of course you could manually remove it from the generated file.

At this point, you have the interface.  Now you need the implementation. In Visual Studio, if you begin to type in a class definition, and then type in a colon and specify the name of an interface, you can right click on that interface and ask Visual Studio to generate method stubs for all the interface methods. Perfect.   As well, you will need to decorate your WCF Service class with the ServiceBehavior attribute.   It looks like this:

     [ServiceBehavior(Name="WcfResearchLibrary",
                     Namespace="urn:Microsoft.Search",
                     IncludeExceptionDetailInFaults=true)]

 

After that, you fill in the implementation for those methods.  Then you need to consider the service host for your app.  If it will run in IIS, then you code up a .svc file;  if you want to host it in a console app, then you use some of the boilerplate I mentioned in a previous post. And so on.

Then you need to deal with the configuration settings, another issue I dealt with in the previous post.  And that's pretty much it.   

You have now coded a WCF Service using WSDL-First design principles.