Web Service Clients

Now that you鈥檝e seen Web services up close and personal, it鈥檚 time to learn about Web service clients鈥攖hat is, applications that use, or consume, Web methods. It鈥檚 easy to write Web services. Writing Web service clients is even easier, thanks to some high-level support lent by the .NET Framework class library (FCL) and a code-generator named Wsdl.exe. If you have a WSDL contract describing a Web service (or the URL of a DISCO file that points to a WSDL contract), you can be making calls to that Web service in no time.

Web Service Proxies

The key concept to grasp when writing Web service clients is that of the Web service proxy. A Web service proxy is an object that provides a local representation of a remote Web service. A proxy is instantiated in the client鈥檚 own application domain, but calls to the proxy flow through the proxy and out to the Web service that the proxy represents. The Wsdl.exe utility that comes with the .NET Framework SDK (and that is integrated into Visual Studio .NET) generates Web service proxy classes from WSDL contracts. Once a proxy is created, calling the corresponding Web service is a simple matter of calling methods on the proxy, as shown here:

CalculatorWebService聽calc聽=聽new聽CalculatorWebService聽();
int聽sum聽=聽calc.Add聽(2,聽2);

The methods in the proxy class mirror the Web methods in the Web service. If the Web service exposes Web methods named Add and Subtract, the Web service proxy also contains methods named Add and Subtract. When you call one of these methods, the proxy packages up the input parameters and invokes the Web method using the protocol encapsulated in the proxy (typically SOAP). The proxy insulates you from the low-level details of the Web service and of the protocols that it uses. It even parses the XML that comes back and makes the result available as managed types.

Using Wsdl.exe to generate a Web service proxy is simplicity itself. Suppose you want to call a Web service whose URL is http://www.wintellect.com/calc.asmx. If the Web service was written with the .NET Framework, which means you can retrieve a WSDL contract by appending a ?wsdl query string to the service URL, you can generate a proxy for the Web service like this:

wsdl聽http://www.wintellect.com/calc.asmx?wsdl

Or you can leave off the query string and let Wsdl.exe supply it for you:

wsdl聽http://www.wintellect.com/calc.asmx

If Calc.asmx wasn鈥檛 written with the .NET Framework, it might not support WSDL query strings. In that case, you find the WSDL contract and pass its URL (or local path name) to Wsdl.exe. The following example assumes that the contract is stored in a local file named Calc.wsdl:

wsdl聽calc.wsdl

However you point it to the WSDL contract, Wsdl.exe generates a CS file containing a class that represents the Web service proxy. That鈥檚 the class you instantiate to invoke the Web service鈥檚 methods.

The proxy class鈥檚 name comes from the service name (that is, the name attribute accompanying the service element) in the WSDL contract. For example, suppose you attribute a Web service as follows in its ASMX file:

[WebService聽(Name="Calculator聽Web聽Service")]

The resulting <service> tag in the WSDL contract looks like this:

<service聽name="Calculator聽Web聽Service">

and the resulting proxy class is named CalculatorWebService. By default, the name of the CS file that Wsdl.exe generates also derives from the service name (for example, Calculator Web Service.cs). You can override that name by passing Wsdl.exe a /out switch. The command

wsdl聽/out:Calc.cs聽http://www.wintellect.com/calc.asmx

names the output file Calc.cs regardless of the service name.

Wsdl.exe supports a number of command line switches that you can use to customize its output. For example, if you鈥檇 prefer the proxy class to be written in Visual Basic .NET rather than C#, use the /language switch:

wsdl聽/language:vb聽http://www.wintellect.com/calc.asmx

If you鈥檇 like Wsdl.exe to enclose the code that it generates in a namespace (which is extremely useful for preventing collisions between types defined in the generated code and types defined in your application and in the FCL), use the /namespace switch:

wsdl聽/namespace:Calc聽http://www.wintellect.com/calc.asmx

Classes generated by Wsdl.exe derive from base classes in the FCL鈥檚 System.Web.Services.Protocols namespace. By default, a proxy class derives from SoapHttpClientProtocol, which enables it to invoke Web methods using SOAP over HTTP. You can change the invocation protocol with Wsdl.exe鈥檚 /protocol switch. The command

wsdl聽/protocol:httpget聽http://www.wintellect.com/calc.asmx

creates a Web service proxy that derives from HttpGetClientProtocol and calls Web methods using HTTP GET commands, while the command

wsdl聽/protocol:httppost聽http://www.wintellect.com/calc.asmx

creates a proxy that derives from HttpPostClientProtocol and uses HTTP POST. Why would you want to change the protocol that a proxy uses to invoke Web methods? In the vast majority of cases, SOAP is fine. However, if the methods that you鈥檙e calling are simple methods that use equally simple data types, switching to HTTP GET or POST makes calls slightly more efficient by reducing the amount of data transmitted over the wire.

Incidentally, if you use Visual Studio .NET to write Web service clients, you don鈥檛 have to run Wsdl.exe manually. When you use the Add Web Reference command found in the Project menu, Visual Studio .NET runs Wsdl.exe for you and adds the proxy class to your project. Add Web Reference also speaks the language of UDDI, making it easy to search Microsoft鈥檚 UDDI registry for interesting Web services.

A Simple Web Service Client

Want to write a client for Calc.asmx? Here are the steps:

  1. Use Wsdl.exe to create a proxy class for Calc.asmx. If you installed Calc.asmx in wwwroot, the proper command is

    wsdl聽http://localhost/calc.asmx

    Wsdl.exe responds by creating a file named Calculator Web Service.cs.

  2. Create a new text file named CalcClient.cs and enter the code in Figure 11-9.

  3. Compile the CS files into a console application with the following command:

    csc聽CalcClient.cs "Calculator聽Web聽Service.cs"
  4. Run CalcClient.exe.

    CalcClient.exe instantiates a Web service proxy and calls the service鈥檚 Add method. The resulting output proves beyond the shadow of a doubt that Calc.asmx is smart enough to add 2 and 2 (Figure 11-10).

    CalcClient.cs
    using聽System;
    
    class聽MyApp
    {
    聽聽聽聽public聽static聽void聽Main聽()
    聽聽聽聽{
    聽聽聽聽聽聽聽聽CalculatorWebService聽calc聽=聽new聽CalculatorWebService聽();
    聽聽聽聽聽聽聽聽int聽sum聽=聽calc.Add聽(2,聽2);
    聽聽聽聽聽聽聽聽Console.WriteLine聽("2聽+聽2聽= " +聽sum);
    聽聽聽聽}
    }
    Figure 11-9
    Console client for Calc.asmx.
    Figure 11-10
    Output from CalcClient.exe.
Avoiding Hard-Coded Service URLs

Look through a CS file generated by Wsdl.exe, and you鈥檒l see the Web service proxy class as well as the methods that wrap the Web service鈥檚 Web methods. You鈥檒l also see that the Web service鈥檚 URL is hardcoded into the CS file in the proxy鈥檚 class constructor. Here鈥檚 an example:

public聽CalculatorWebService()聽{
聽聽聽聽this.Url聽= "http://www.wintellect.com/calc.asmx";
}

If the Web service moves, you鈥檒l have to modify the CS file and regenerate the proxy.

To avoid having to update code when a Web service鈥檚 URL changes, you can use Wsdl.exe鈥檚 /appsettingurlkey (abbreviated /urlkey) switch. The command

wsdl聽/urlkey:CalcUrl聽http://www.wintellect.com/calc.asmx

produces the following class constructor:

public聽CalculatorWebService()聽{
聽聽聽聽string聽urlSetting聽=
System.Configuration.ConfigurationSettings.AppSettings["CalcUrl"];
聽聽聽聽if聽((urlSetting聽!=聽null))聽{
聽聽聽聽聽聽聽聽this.Url聽=聽urlSetting;
聽聽聽聽}
聽聽聽聽else聽{
聽聽聽聽聽聽聽聽this.Url聽= "http://www.wintellect.com/calc.asmx";
聽聽聽聽}
}

Now you can assign a value to 鈥淐alcUrl鈥?in the appSettings section of a local Web.config file, like so:

<configuration>
聽聽<appSettings>
聽聽聽聽<add聽key="CalcUrl" value="http://www.wintellect.com/calc.asmx" />
聽聽</appSettings>
</configuration>

If the URL changes, you can update the proxy simply by editing Web.config. No code changes are required.

Asynchronous Method Calls

Something else you鈥檒l notice if you open a CS file generated by Wsdl.exe is that the proxy class contains asynchronous as well as synchronous wrappers around the Web service鈥檚 methods. The former can be used to invoke Web methods asynchronously. An asynchronous call returns immediately, no matter how long the Web service requires to process the call. To retrieve the results from an asynchronous call, you make a separate call later on.

Here鈥檚 an example using Calc.asmx鈥檚 Add method that demonstrates how to invoke a Web method asynchronously. The client calls the proxy鈥檚 BeginAdd method to initiate an asynchronous call and then goes off to attend to other business. Later it returns to finish the call by calling EndAdd:

CalculatorWebService聽calc聽=聽new聽CalculatorWebService聽();
IAsyncResult聽res聽=聽calc.BeginAdd聽(2,聽2,聽null,聽null);
聽聽.
聽聽.
聽聽.
int聽sum聽=聽calc.EndAdd聽(res);

If the call hasn鈥檛 completed when EndAdd is called, EndAdd blocks until it does. If desired, a client can use the IsCompleted property of the IAsyncResult interface returned by BeginAdd to determine whether the call has completed and avoid calling EndAdd prematurely:

IAsyncResult聽res聽=聽calc.BeginAdd聽(2,聽2,聽null,聽null);
聽聽.
聽聽.
聽聽.
if聽(res.IsCompleted)聽{
聽聽聽聽int聽sum聽=聽calc.EndAdd聽(res);
}
else聽{
聽聽聽聽//聽Try聽again聽later
}

Another option is to ask to be notified when an asynchronous call returns by providing a reference to an AsyncCallback delegate wrapping a callback method. In the next example, EndAdd won鈥檛 block because it isn鈥檛 called until the client is certain the method call has returned:

AsyncCallback聽cb聽=聽new聽AsyncCallback聽(AddCompleted);
IAsyncResult聽res聽=聽calc.BeginAdd聽(2,聽2,聽cb,聽null);
聽聽.
聽聽.
聽聽.
public聽void聽AddCompleted聽(IAsyncResult聽res)
{
聽聽聽聽int聽sum聽=聽calc.EndAdd聽(res);
}

Whatever approach you decide on, the proxy鈥檚 asynchronous method鈥揷all support is extraordinarily useful for calling methods that take a long time to complete. Add isn鈥檛 a very realistic example because it鈥檚 such a simple method, but the principle is valid nonetheless.

Web Service Clients and Proxy Servers

If a client invokes methods on a Web service from behind a proxy server, the Web service proxy needs to know the address of the proxy server. You can provide that address in two ways. The first option is to pass Wsdl.exe a /proxy switch specifying the proxy server鈥檚 URL:

wsdl聽/proxy:http://myproxy聽http://www.wintellect.com/calc.asmx

Option number two is to programmatically initialize the Web service proxy鈥檚 Proxy property (which it inherits from HttpWebClientProtocol) with a reference to a WebProxy object (System.Net.WebProxy) identifying the proxy server:

CalculatorWebService聽calc聽=聽new聽CalculatorWebService聽();
calc.Proxy聽=聽new聽WebProxy聽(http://myproxy,聽true);
int聽sum聽=聽calc.Add聽(2,聽2);

The true passed to WebProxy鈥檚 constructor bypasses the proxy server for local addresses. Pass false instead to route all requests through the proxy server.