18.3. Building an XML Web Service Client

 < Day Day Up > 

This section describes how to create a client application that consumes a Web Service. The sobjective is to make the client's call to the remote Web method as simple as calling a method in its own code. Although the actual implementation does not reach quite that level of simplicity, the code is straightforward, and much of it can be generated automatically using .NET tools.

Before delving into details, let's first take a high-level view of how a .NET Web Services client interacts with a Web Service.

The most important thing to observe in Figure 18-6 is that the client does not directly invoke the Web Service method. Instead, it calls a proxy object that performs this task. The proxy class is created from the WSDL information provided by the Web Service. We'll see how to do this shortly. The proxy code is a class that has the same class name and method names as the Web Service class does. It contains the transport logic that allows it to make the actual connection to the Web Service. It may do this either synchronously (without receiving confirmation) or asynchronously. The messages exchanged between the proxy and server are bundled within an HTTP request and transmitted using either the HTTP or the SOAP wire protocol.

Figure 18-6. Overview of how a client accesses a Web Service


Creating a Simple Client to Access the Web Service Class

To demonstrate the basic principles involved in creating a Web Service client, let's develop a console client application to access the BirthDayWS Web Service (refer to Figure 18-1). The client passes three arguments to the service and prints the string it returns. Recall that the service consists of the Web class Birthday and a single method GeTDayBorn:

 public class BirthDay {    [System.Web.Services.WebMethod         (Description="Return day of week for a date")] public string GetDayBorn(int mo, int day, int yr) 

Although the Web Service method can reside on a remote machine anywhere on the Internet, we can approach the client design as if we were within the same assembly. Here is the client code contained in the file bdClient.cs:

 using System; using System.Web.Services; public class BirthDayClient {    static void Main(string[] args)    {       BirthDay bd = new BirthDay();       string dayOfWeek = bd.GetDayBorn(12,20,1963);       Console.WriteLine(dayOfWeek);    } } 

Compiling this, of course, results in an error stating that BirthDay and bd cannot be found. We resolve this by creating a proxy class that performs the remote call, yet can be accessed locally by the client. The code for the proxy class is obtained by feeding the WSDL information that defines the Web Service into the .NET wsdl.exe utility.

Using wsdl.exe to Create a Proxy

The wsdl.exe utility reads the WSDL describing a Web Service and generates the source code for a proxy class to access the service; it can also use the information to create the skeleton code for a Web Service. This latter feature is designed for developers who prefer to design the WSDL as the first step in creating a Web Service. We do not take that approach in this chapter, but you should be aware that there are WSDL editors available for that task.

The wsdl.exe utility is run from the command line and has numerous flags or options to govern its execution. Table 18-1 lists those most commonly used.

Table 18-1. wsdl.exe Command-Line Options

Option

Description

 /appsettingurlkey /urlkey: 

Specifies a key within the client's *.config file that contains the URL of the Web Service. The default is to hardcode the URL within the proxy class.

 /language /l: 

Specifies the language to use for generating the proxy class. Choices are:

CS (C#), VB (Visual Basic), JS (JScript), and VJS (Visual J#).

C# is the default.

 /namespace /n: 

Specifies the namespace for the proxy.

/out

Specifies file in which the generated proxy code is placed. The default is to use the XML Web Service name with an extension reflecting the language used for the code.

/protocol

The wire protocol to use within the proxy code:

/protocol:SOAP SOAP 1.1 is generated

/protocol:SOAP12 SOAP 1.2 is generated

/protocol:HttpGet or /protocol:HttpPost

/server

Generates an abstract class for the Web Service. This is used when the WSDL document is used to create a Web Service.

/serverinterface

Generates interfaces rather than abstract classes for the Web Service. Available only in 2.0 and later.


The following statement creates a proxy class from the BirthDayWS.asmx Web Service and places it in c:\BDProxy.cs:

 wsdl.exe /out:c:\BDProxy.cs http://localhost/ws/ BirthDayWS.asmx?WSDL 

This proxy source code can be used in two ways: include it in the client's source code, or compile it into a DLL and add a reference to this DLL when compiling the client code. Let's look first at the DLL approach. This command line compiles the source code and links two DLL files containing classes required by the proxy:

 csc /t:library /r:system.web.services.dll /r:system.xml.dll          BDProxy.cs 

We are now ready to compile the client source code into an executable file, bdClient.exe:

 csc /r:BDProxy.dll  bdClient.cs 

If we add the proxy code directly to the bdClient.cs file and compile it, the result is a module that produces the same output as a version that links the proxy as a separate DLL file.

Core Note

When wsdl.exe creates proxy code, it checks the associated Web Service for compliance with the WS-I Basic Profile 1.0 standards and emits a warning if the service is noncompliant. For information on this standard, refer to the Web Services Interoperability Organization's Web site at http://www.ws-i.org.


Listing 18-2 examines the proxy source code to understand how it converts a client's call into a remote Web Service invocation.

Listing 18-2. Proxy Source Code to Access BirthDayWS.asmx Web Service
 using System; using System.ComponentModel; using System.Diagnostics; using System.Web.Services; using System.Web.Services.Protocols; using System.Xml.Serialization; // // Auto-generated by wsdl, Version=2.0.40607.16. [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] [System.Web.Services.WebServiceBindingAttribute(        Name="BirthDaySoap",        Namespace="http://tempuri.org/")] public class BirthDay : SoapHttpClientProtocol {    private System.Threading.SendOrPostCallback          GetDayBornOperationCompleted;    public BirthDay() {          this.Url = "http://localhost/ws/BirthDayWS.asmx";    }    public event GetDayBornCompletedEventHandler          GetDayBornCompleted;    // (1) Synchronous Call to Web Service    [SoapDocumentMethodAttribute(       "http://tempuri.org/GetDayBorn",       RequestNamespace="http://tempuri.org/",       ResponseNamespace="http://tempuri.org/",       Use=       System.Web.Services.Description.SoapBindingUse.Literal,       ParameterStyle=       System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]    public string GetDayBorn(int mo, int day, int yr)    {       object[] results = this.Invoke("GetDayBorn", new object[] {             mo,             day,             yr});       return ((string)(results[0]));    }    // (2) Asynchronous call to Web Service    public System.IAsyncResult BeginGetDayBorn(int mo, int day,          int yr, System.AsyncCallback callback,          object asyncState) {       return this.BeginInvoke("GetDayBorn", new object[] {             mo,             day,             yr}, callback, asyncState);    }    public string EndGetDayBorn(System.IAsyncResult asyncResult) {          object[] results = this.EndInvoke(asyncResult);          return ((string)(results[0]));    }    // (3) Call this for event-based asynchronous handling    public void GetDayBornAsync(int mo, int day, int yr) {          this.GetDayBornAsync(mo, day, yr, null);    }    public void GetDayBornAsync(int mo, int day, int yr,          object userState) {       if ((this.GetDayBornOperationCompleted == null)) {             this.GetDayBornOperationCompleted = new             System.Threading.SendOrPostCallback(                this.OnGetDayBornOperationCompleted);       }       this.InvokeAsync("GetDayBorn", new object[] {             mo,             day,             yr}, this.GetDayBornOperationCompleted,             userState);    }    private void OnGetDayBornOperationCompleted(object arg) {       if ((this.GetDayBornCompleted != null)) {             InvokeCompletedEventArgs invokeArgs =                ((InvokeCompletedEventArgs)(arg));             this.GetDayBornCompleted(this, new                GetDayBornCompletedEventArgs(invokeArgs.Results,                invokeArgs.Error, invokeArgs.Cancelled,                invokeArgs.UserState));       }    }    public new void CancelAsync(object userState) {          base.CancelAsync(userState);    } } public delegate void GetDayBornCompletedEventHandler(       object sender, GetDayBornCompletedEventArgs args); public class       GetDayBornCompletedEventArgs :AsyncCompletedEventArgs {    private object[] results;    internal GetDayBornCompletedEventArgs(object[] results,       System.Exception exception, bool cancelled,       object userState) : base(exception, cancelled, userState) {          this.results = results;    }    // Results are available as a property    public string Result {      get {            this.RaiseExceptionIfNecessary();            return ((string)(this.results[0]));       }    } } 

Observe that the proxy class has the same name as the Web Service class (Birthday) it represents, and implements a method (GeTDayBorn) having the same name as the Web Service method. The supporting code is quite different, however. It contains transport logic rather than application logic. This code uses methods provided by the System.Web.Services.Protocols.SoapHttpClientProtocol class from which the proxy class derives. Table 18-2 summarizes of the more useful members of the class.

Table 18-2. Selected Members of SoapHttpClientProtocol

Member

Description

BeginInvoke()

Begins an asynchronous invocation of the Web method.

EndInvoke()

Ends an asynchronous invocation of the Web method.

Invoke()

Invokes the Web method synchronously.

CookieContainer

Gets or sets the collection of cookies. This permits a proxy-based client to accept cookies and allow a server to maintain state information.

Proxy

Gets or sets proxy information needed to make a Web Service call through a firewall.

Timeout

Gets or sets the timeout (in milliseconds) a client waits for a synchronous call to a Web Service to be completed.

Url

Gets or sets the URL used to access the Web server.


Let's look at how these members are used in a proxy class.

Synchronous Calls to a Web Service Method

A proxy provides the capability to invoke a Web Service method synchronously or asynchronously. Recall from Chapter 13, "Asynchronous Programming and Multithreading," that a synchronous operation uses only one thread, which is blocked until control returns from the called method. An asynchronous call, on the other hand, creates a worker thread that handles the operations of the called method. Control returns immediately to the main thread while a separate thread handles the auxiliary task. The calling routine is notified when the task is completed.

Synchronous communication is implemented with a proxy method that has the same name as the Web method and uses the Invoke method to request the service.

 public string GetDayBorn(int mo, int day, int yr) {      object[] results = this.Invoke("GetDayBorn", new object[] {            mo, day, yr});      return ((string)(results[0])); } 

This is the most intuitive way to access a service because a client can be totally unaware of the proxy and execute a call to the Web Service method, using its actual name. However, because a synchronous call by definition does not require a response, the client code should include a timeout interval and handle any exception that occurs if the interval is exceeded. The following code sets a timeout of four seconds and handles any System.Net.WebException that is thrown.

 // Must add using System.Net; BirthDay bd = new BirthDay(); bd.Timeout= 4000;         // Set timeout to 4 seconds try {    string dayOfWeek = bd.GetDayBorn(12,20,1963);    Console.WriteLine(dayOfWeek); } catch (WebException ex) {    Console.WriteLine(ex.Message);  // Will report timeout } catch (Exception ex) {    Console.WriteLine("Unknown error occurred."); } 

Core Note

An easy way to test the timeout exception is to add a Thread.Sleep() call within the Web method being called on the Web server. This suspends thread execution for a specified amount of time. For example:

 System.Threading.Thread.Sleep(3000);  // Sleep 3 seconds 


Asynchronous Calls to a Web Service Method

Asynchronous communication is performed using the Beginmethodname and Endmethodname methods supplied by the proxy. Internally, these call the BeginInvoke and EndInvoke methods, respectively. BeginInvoke queues the method to be run on a separate (worker) thread. It returns an IAsyncResult type that is subsequently passed to the EndInvoke method to retrieve the results. As shown in the following code, the IAsyncResult type is also used to determine if the call has been completed.

 BirthDay bd = new BirthDay(); // Pass null callback argument since we will poll status IAsyncResult dayOfWeek =bd.BeginGetDayBorn(12,20,1963,null,null); // Perform other operations here while waiting for response If (dayOfWeek.IsCompleted)       Console.Write(bd.EndGetDayBorn(dayOfWeek)); 

This "polling" approach can be used to periodically check the status of the request. Another approach is to have the Web Service call a method in the client code when the response is ready. The AsyncCallback delegate is used to specify the method that receives the callback.

 // Create delegate that specifies method to be called back AsyncCallback wscb = new AsyncCallback(DayReturned); BirthDay bd = new BirthDay(); IAsyncResult dayOfWeek =       bd.BeginGetDayBorn(12,20,1963,wscb,null); // This method is called back when the web method completes public static void DayReturned(IAsyncResult result) {       BirthDay bd = new BirthDay();       Console.WriteLine(bd.EndGetDayBorn(result)); } 

As with polling, the method implementing EndInvoke (EndGetDayBorn, in this example) is called to return the result.

Event-Based Asynchronous Calls

If you take a close look at the proxy code in Listing 18-2, you'll notice that it includes a delegate and corresponding event declaration:

 public event GetDayBornCompletedEventHandler       GetDayBornCompleted; public delegate void GetDayBornCompletedEventHandler(       object sender, GetDayBornCompletedEventArgs args); 

These enable client code to treat the Web Service invocation as an event that can be handled by a local event handler. Only a few lines of code are required to hook up an event handler to the event defined in the proxy:

 Birthday service = new Birthday(); // (1) Associate event handler with event service.GetDayBornCompleted += new       GetDayBornCompletedEventHandler(this.ShowOutput); // (2) Invoke service asynchronously service.GetDayBornAsync(); // (3) Event handler called when service returns value private void ShowOutput(object sender,       GetDayBornCompletedEventArgs args) {    Console.WriteLine(args.Result); } 

The Web Service is invoked asynchronously by calling the proxy provided method GetdayBornAsync (<web service name>Async). When the Web Service finishes execution, .NET returns the results to the event handler through the args parameter.

Using the CookieContainer Property to Maintain Session State

Setting a Web Service method's EnableSession attribute to true enables the method to maintain state information. However, this is only effective if the client can accept cookies. This is not usually a problem with browsers, but Windows Forms (WinForms) applications do not store cookies by default. The CookieContainer property is a way to permit WinForms clients to accept cookies.

To permit the client to accept cookies, set the CookieContainer property of the proxy class instance to a CookieContainer object. The code shown here permits the Web Service to maintain state information over all queries made by this client.

 // BirthDay bd = new BirthDay() .. must have class level scope if (bd.CookieContainer == null)    bd.CookieContainer = new CookieContainer(); IAsyncResult dayOfWeek =  bd.BeginGetDay- Born(mo,dy,yr,null,null); Console.WriteLine(bd.EndGetDayBorn(dayOfWeek)); // List all cookies in CookieContainer collection // Create a Uniform Resource Identifier object Uri hostURL = new Uri("http://localhost"); foreach (Cookie ck in bd.CookieContainer.GetCookies(hostURL)) Console.WriteLine(ck.Name+" "+ck.TimeStamp.ToString()); 

Notice that the CookieContainer class has a method GetCookies that returns a collection of cookies stored in the container. In the example, foreach is used to enumerate the collection and list the name of each cookie along with the date it was created.

Creating a Proxy with Visual Studio.NET

We have shown how the proxy code can be generated using wsdl.exe and compiled into a DLL or used as source code within the client application. If you use VS.NET to create your Web Service client, you have no need for the wsdl.exe utility. Instead, VS.NET lets you select a Web Service for which you want to generate the proxy code. It adds the code to the project, and you can use the techniques described in the preceding examples to access it. Here are the details:

1.

Open up .NET and select a Windows application.

2.

Select Add Web Reference under the Project menu tab. The subsequent screen contains a text box at the top in which you enter the URL of the Web Service. For example:

 http://localhost/ws/BirthDayWS.asmx 

3.

Press the Enter key and a screen appears that includes a Web Service Help page for the selected service. Click the Add Reference button and the source code containing the proxy is added to the project.

4.

The proxy code has its own namespace that should be added to the client code. In this example, wsdlclient is the name of the VS.NET project.

 using wsdlclient.localhost; 

You will see that a Web References folder has been added to the directory of the project. To view the proxy source, click the Show All Files buttons in the Solution Explorer and open the Reference.cs file beneath the localhost node. Notice that in addition to the proxy code, the directory includes the WSDL file from which the proxy was generated. We'll look at this file in detail in the next section to gain an understanding of how WSDL describes a Web Service. In addition, we will look at how SOAP is used to define the format of messages transported between the Web Service consumer and provider.

     < Day Day Up > 


    Core C# and  .NET
    Core C# and .NET
    ISBN: 131472275
    EAN: N/A
    Year: 2005
    Pages: 219

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net