Asynchronous Web Services


In addition to causing work to happen on another thread, you'll also want to cause work to happen on other machines, which is an ideal use of web services. Calling a web service is similar to passing a message between threads, except that web services messages travel between machines using standard protocols such as HTTP and XML.

Imagine a .NET web service that calculates digits of pi using a version of CalcPi that's been modified to handle web service disconnection mid-calculation:

// CalcPiService.cs class CalcPiService : System.Web.Services.WebService {   [WebMethod]   public string CalcPi(int digits) {     StringBuilder pi = new StringBuilder("3", digits + 2);     if( digits > 0 ) {       pi.Append(".");       for( int i = 0; i < digits; i += 9 ) {         // Calculate next i decimal places         ...         // End execution if client disconnects from web service         if( !Context.Response.IsClientConnected )           break;       }     }     return pi.ToString();   } }


Now imagine a version of the CalcPi program that uses the web service instead of our slow client-side algorithm to calculate pi on giant machines with huge processors (or even better, databases with more digits of pi cached than anyone could ever want or need). The underlying protocol of web services is HTTP- and XML-based, and we could readily form a web service request to ask for the digits of pi we're after. Still, it's simpler to let VS05 generate a class to make the web services calls for you.

You do this in the Project menu using the Add Web Reference item. The Add Web Reference dialog, shown in Figure 18.16, allows you to enter the URL of the WSDL (Web Services Description Language) that describes the web service you'd like to call.

Figure 18.16. Adding a Web Reference to the CalcPiWebService WSDL


For example, after installing the web service sample you'll find at our web site, you can access the WSDL via the following URL:

http://localhost/CalcPiWebService/CalcPiService.asmx?WSDL


Accepting the WSDL in the Add Web Reference dialog generates a client-side web services proxy class, a helper class that turns your method calls into web services messages.[5] The generated proxy code for the CalcPi web service looks like this:

[5] Internally, the generated proxy class stores the web service's URL as an application setting of the special type "(Web Service)". The naming convention conforms to the following: namespace_webReferenceName_webServiceName.

using System.ComponentModel;  using System.Web.Services;  namespace WebServiceCalcPiSample.CalcPiWebService {    [WebServiceBinding(   Name = "CalcPiServiceSoap", Namespace = "http://tempuri.org/")] class CalcPiService : SoapHttpClientProtocol {   // Properties   public string Url { get; set; }   public bool UseDefaultCredentials { get; set; }   // Methods   public CalcPiService();     [SoapDocumentMethod(     "http://tempuri.org/CalcPi",     RequestNamespace="http://tempuri.org/",     ResponseNamespace="http://tempuri.org/",     Use=System.Web.Services.Description.SoapBindingUse.Literal,     ParameterStyle=Protocols.SoapParameterStyle.Wrapped)]   public string CalcPi(int digits);   public void CalcPiAsync(int digits);   public void CalcPiAsync(int digits, object userState);   public void CancelAsync(object userState);   // Events   public event CalcPiCompletedEventHandler CalcPiCompleted; } // AsyncCompletedEventArgs is new in .NET 2.0 class CalcPiCompletedEventArgs : AsyncCompletedEventArgs {   // Properties   public string Result { get; } } delegate void CalcPiCompletedEventHandler(   object sender,   CalcPiCompletedEventArgs e); }


Because web services make calls across machine (and often network) boundaries, you should assume they'll take a long time, and, if called synchronously, they'll block the UI thread. You can use the standard techniques discussed in this chapter to call web service methods asynchronously. But as you can tell in the generated proxy code, there's built-in support for asynchronous operations via the MethodName Async and CancelAsync methods, one for each method on the web service.

The first step in retrofitting the sample application to use the web service is to call the web service proxy's CalcPiAsync method:

// AsyncCalcPiForm.cs partial class AsyncCalcPiForm : Form {   bool isBusy = false;   bool cancellationPending = false;   ...   void calcButton_Click(object sender, EventArgs e) {     // Don't process if cancel request pending     if( this.cancellationPending ) return;     // Is web service currently executing?     if( this.isBusy ) {       // Cancel asynchronous pi calculations       this.service.CancelAsync(null);       this.cancellationPending = true;     }     else {       // Start calculating pi asynchronously       this.calcButton.Text = "Cancel";       this.resultsTextBox.Text = "";       this.isBusy = true;       this.service.CalcPiAsync(         (int)this.decimalPlacesNumericUpDown.Value);     }   }   ... }


Notice that this code looks similar to the CalcPi sample, which used BackgroundWorker. This is because the generated proxy is built on the same .NET-provided threading infrastructure that BackgroundWorker is. Unfortunately, it's not as advanced; you don't have properties that tell you whether the worker thread is busy, or whether a cancellation is pending. Because of the dynamic nature of the generated proxy class and web services in general, tackling this problem would be tricky. However, you can easily use your own state member variables to do so, as this sample does. To cancel a web method call, you simply call the CancelAsync method.

If you are interested in the web method's response, the next step is to register with an event implemented by the generated proxy that uses the MethodName Completed naming convention:

// AsyncCalcPiForm.cs partial class AsyncCalcPiForm : Form {   ...   public AsyncCalcPiForm() {      InitializeComponent();     this.service.CalcPiCompleted += service_CalcPiCompleted;   }   void calcButton_Click(object sender, EventArgs e) {...}   ... } 


The handler you register also looks similar to the BackgroundWorker samples:

// AsyncCalcPiForm.cs partial class AsyncCalcPiForm : Form {   ...   void service_CalcPiCompleted(     object sender,     CalcPiCompletedEventArgs e) {     Debug.Assert(this.InvokeRequired == false);     if( this.InvokeRequired == true ) throw new Exception("Doh!");     // Reset UI state     this.calcButton.Text = "Calculate";     // We're not busy anymore     this.isBusy = false;     // Was there an error?     if( e.Error != null ) {       this.resultsTextBox.Text = e.Error.Message;       return;     }     // Was the worker thread canceled?     if( e.Cancelled ) {       this.resultsTextBox.Text = "Canceled";       // Allow calculations to start       this.cancellationPending = false;     }   }   ... }


This code sets the state member variablesisBusy and cancellationPendingdepending on how the web method call ended. The code also checks for exceptions, something that is particularly important given the less resilient nature of using the web. Figure 18.17 shows what happens when a connection to the web service is lost mid-call.

Figure 18.17. The Result of a Lost Connection Mid-Call


The code to operate web services turns out to be relatively lightweight, thanks to the generated proxy class added to your project when you add a web service reference.

Web Service Components

You can enjoy a slightly more Designer-driven experience by using the component that VS05 generates for each referenced web service, as shown in Figure 18.18.

Figure 18.18. A Web Service Component


As a component, a web service can be dragged and dropped right onto your form. The main benefit is that you gain full Properties window-driven configuration of the service, as shown in Figure 18.19.

Figure 18.19. Configuring a Web Service Component


Additionally, each MethodName Completed event exposed by a web service component is available from the Properties window, as shown in Figure 18.20, thus allowing you to leverage the Windows Forms Designer's ability to automatically hook up those events for you.

Figure 18.20. Hooking the MethodName Completed Events Automatically


Updating our asynchronous web service code to use the component directly produces the following result.

// AsyncCalcPiForm.cs  partial class AsyncCalcPiForm : Form {    bool isBusy = false;    bool cancellationPending = false;    public AsyncCalcPiForm() {      InitializeComponent();    }    void calcButton_Click(object sender, EventArgs e) {      // Don't process if cancel request pending      if( this.cancellationPending ) return;      // Is web service currently executing?      if( isBusy ) {        // Cancel asynchronous pi calculations        this.calcPiServiceComponent.CancelAsync(null);        this.cancellationPending = true;      }      else {        // Start calculating pi asynchronously        this.calcButton.Text = "Cancel";        this.resultsTextBox.Text = "";        this.isBusy = true;        this.calcPiServiceComponent.CalcPiAsync(           (int)this.decimalPlacesNumericUpDown.Value);      }    }  void calcPiServiceComponent_CalcPiCompleted(    object sender,    CalcPiCompletedEventArgs e) {      Debug.Assert(this.InvokeRequired == false);      if( this.InvokeRequired == true ) throw new Exception("Doh!");      // Reset UI state      this.calcButton.Text = "Calculate";      // We're not busy anymore      this.isBusy = false;      // Was there an error?      if( e.Error != null ) {        this.resultsTextBox.Text = e.Error.Message;        return;      }      // Was the worker thread canceled?      if( e.Cancelled ) {        this.resultsTextBox.Text = "Canceled";        // Allow calculations to start        this.cancellationPending = false;      }      // Display result      this.resultsTextBox.Text = e.Result;   } }


The code isn't dramatically smaller, although producing it is slightly faster. It also leaves open the possibility of performing further configurations via the Properties window, including binding web service properties to application and user settings.




Windows Forms 2.0 Programming
Windows Forms 2.0 Programming (Microsoft .NET Development Series)
ISBN: 0321267966
EAN: 2147483647
Year: 2006
Pages: 216

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