Creating Clients with the .NET Framework SDK





Creating Clients with the .NET Framework SDK

The .NET Framework SDK comes with two command-line tools for building XML Web service clients: disco.exe and wsdl.exe. The first, disco.exe, uses the DISCO (Web Services Discovery Language) specification to download any files, including WSDL descriptions and XML schemas, needed to describe a Web service completely. Then, wsdl.exe transforms these WSDL descriptions into client code.

The programming model for clients is very similar to the model for servers: It uses classes and methods for services and their operations. For each service described in a WSDL document, a client proxy class is created. For every operation within that service, three methods are created: one synchronous method, and two other methods for calling the operation asynchronously.

Discovery with disco.exe

The command-line tool disco.exe will download every file listed in a DISCO document. DISCO is a specification (although not a standard) for two things:

  • A packaging format

  • An inspection routine

The packaging format is an XML schema that describes how to list services. It also enables linking to other DISCO documents. Even better, these DISCO documents contain links to schemas and even SOAP binding information on the services.

If you want, you can write DISCO documents yourself. However, .NET will automatically generate DISCO documents for you, just as it does with WSDL files. All you need to do is add ?DISCO at the end of the URL, as in http://localhost/test/test.asmx?DISCO. Listing 4.1 shows an example of a simple DISCO document that .NET will automatically generate for you.

An Automatically Generated DISCO Document
<?xml version="1.0" encoding="utf-8"?>
<discovery
     xmlns:xsd="http://www.w3.org/2001/XMLSchema"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns="http://schemas.xmlsoap.org/disco/"
>
<contractRef
     ref="http://localhost/ExceptionE/Service1.asmx?wsdl"
     docRef="http://localhost/ExceptionE/Service1.asmx"
     xmlns="http://schemas.xmlsoap.org/disco/scl/"
/>
<soap
     address="http://localhost/ExceptionE/Service1.asmx"
     xmlns:q1="http://tempuri.org/"
     binding="q1:Service1Soap"
     xmlns="http://schemas.xmlsoap.org/disco/soap/" />
</discovery>

The inspection routine from the DISCO specification states that DISCO documents can link to other DISCO documents. Furthermore, it provides a way to link to DISCO documents from within HTML. So, whenever disco.exe or any other tool that understands the DISCO inspection routine goes to an HTML page with that link, it can go forward and download the actual DISCO document to which the HTML page is pointing.

The disco.exe command-line tool will download any files listed in the DISCO document it discovers. It will then create a new file called results.discomap that lists of all the files it downloaded. Listing 4.2 shows the .discomap file that is created by disco.exe.

An Example .discomap File
<?xml version="1.0" encoding="utf-8"?>
<DiscoveryClientResultsFile
     xmlns:xsd="http://www.w3.org/2001/XMLSchema"
     xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance>
<Results>
     <DiscoveryClientResult
referenceType="System.Web.Services.Discovery DiscoveryDocumentReference"
     url="http://localhost/ExceptionE/Service1.asmx?disco"
     filename="Service1.disco" />
    <DiscoveryClientResult
      referenceType="System.Web.Services.Discovery.ContractReference"
      url="http://localhost/ExceptionE/Service1.asmx?wsdl"
      filename="Service1.wsdl" />
</Results>
</DiscoveryClientResultsFile>

At this point, you can use wsdl.exe to consume these files.

WSDL Consumption

The command-line tool for generating code from a WSDL file is wsdl.exe. The code it generates consists of a proxy class that exposes methods for each Web service operation, and then classes (which look very similar to structs) for any complexType definitions in the schema section of the WSDL.

Generally, you can supply wsdl.exe with either (a) the URL for the location of a WSDL file or (b) the location of a .discomap file. Either way, it will generate a class. You can also indicate on the command line which language to generate the classes in. The default is C#.

The mapping from WSDL to classes is fairly simple: Only one binding will be used, and then for each operation within that binding, a synchronous method and two asynchronous methods will be generated inside of a single class. Listing 4.3 shows this.

A Sample WSDL File
<?xml version="1.0" encoding="utf-8"?>
<definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
     xmlns:s="http://www.w3.org/2001/XMLSchema"
     xmlns:s0="http://tempuri.org/"
     xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
     targetNamespace="http://tempuri.org/"
     xmlns="http://schemas.xmlsoap.org/wsdl/">
  <types>
    <s:schema
          elementFormDefault="qualified"
          targetNamespace="http://tempuri.org/">
      <s:element name="Divide">
        <s:complexType>
          <s:sequence>
            <s:element
               minOccurs="1"
               maxOccurs="1"
               name="x"
               type="s:double" />
            <s:element
               minOccurs="1"
               maxOccurs="1"
               name="y"
               type="s:double" />
          </s:sequence>
        </s:complexType>
      </s:element>
      <s:element name="DivideResponse">
        <s:complexType>
          <s:sequence>
            <s:element
                minOccurs="1"
                maxOccurs="1"
                name="DivideResult"
                type="s:double" />
          </s:sequence>
        </s:complexType>
      </s:element>
    </s:schema>
  </types>
  <message name="DivideSoapIn">
    <part name="parameters" element="s0:Divide" />
  </message>
  <message name="DivideSoapOut">
    <part name="parameters" element="s0:DivideResponse" />
  </message>
  <portType name="Service1Soap">
    <operation name="Divide">
      <input message="s0:DivideSoapIn" />
      <output message="s0:DivideSoapOut" />
    </operation>
  </portType>
  <binding name="Service1Soap" type="s0:Service1Soap">
    <soap:binding
         transport="http://schemas.xmlsoap.org/soap/http"
         style="document" />
    <operation name="Divide">
      <soap:operation
         soapAction="http://tempuri.org/Divide"
         style="document" />
      <input>
        <soap:body use="literal" />
      </input>
      <output>
        <soap:body use="literal" />
      </output>
    </operation>
  </binding>
  <service name="Service1">
    <port name="Service1Soap" binding="s0:Service1Soap">
      <soap:address
        location="http://localhost/ExceptionE/Service1.asmx" />
    </port>
  </service>
</definitions>

The WSDL in Listing 4.3 generates a C# class that looks like Listing 4.4.

A Proxy Class Generated by wsdl.exe
using System.Diagnostics;
using System.Xml.Serialization;
using System;
using System.Web.Services.Protocols;
using System.ComponentModel;
using System.Web.Services;

/// <remarks/>
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Web.Services.WebServiceBindingAttribute(
        Name="Service1Soap",
        Namespace="http://tempuri.org/")]
public class Service1 : System.Web.Services.Protocols.SoapHttpClientProtocol {

    public Service1() {
        this.Url = "http://localhost/ExceptionE/Service1.asmx";
    }

    [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/Divide", RequestNamespace="http://tempuri.org/",ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle. Wrapped!!
)]
    public System.Double Divide(System.Double x, System.Double y) {
        object[] results = this.Invoke("Divide", new object[] {
                    x,
                    y});
        return ((System.Double)(results[0]));
    }

    public System.IAsyncResult BeginDivide(
            System.Double x,
            System.Double y,
            System.AsyncCallback callback,
            object asyncState) {
        return this.BeginInvoke("Divide", new object[] {
                    x,
                    y}, callback, asyncState);
    }
    public System.Double EndDivide(System.IAsyncResult asyncResult) {
        object[] results = this.EndInvoke(asyncResult);
        return ((System.Double)(results[0]));
    }
}

The class in Listing 4.4 is derived from SoapHttpClientProtocol. It has a method called Invoke that does most of the interesting and hard work. And as you can see, the attributes used throughout this class help the infrastructure shape the XML for the correct message.

The [WebServiceBinding] attribute specifies the SOAP binding that this client uses, and sets the namespace of this service. It's interesting that the attributes used on the client are the same as the ones used on the service, but the semantics of the attributes usage changes.

The [SoapDocumentMethod] attribute, which is put on each method that is generated, includes some valuable information, such as the namespace of the method, its use (literal or encoded), and the parameter style. These are all things that will be familiar to you from the server side of things. But of importance here are the implementation details. If the WSDL that generated this class was from a server type other than .NET, then you would still have these.

The other interesting tidbit about this proxy class is the number of methods generated. I simplified matters earlier when I said that one synchronous method was created for each Web service operation. In addition, two asynchronous methods are created, because that is how the .NET framework does asynchronous work. Later in this chapter, we will discuss how to use these methods.

Of course, you don't always need to go through disco.exe to build a client proxy. Often, you can just give the URL, or file path, to the WSDL file. In this case, a proxy class will be created just the same. Why use disco.exe at all? Because often you will want to have a build process in which you consume multiple services at once to build multiple clients.


     Python   SQL   Java   php   Perl 
     game development   web development   internet   *nix   graphics   hardware 
     telecommunications   C++ 
     Flash   Active Directory   Windows