Asynchronous Web Services


One specific area in which you'll want to use asynchronous WinForms applications is when calling 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. [4] Imagine a .NET Web service that calculated digits of pi using some way-cool fast pi calculation engine:

[4] For thorough coverage of the whys and wherefores of Web services, you can read .NET Web Services: Architecture and Implementation with .NET (Addison-Wesley, 2003), by Keith Ballinger.

  public class CalcPiService : System.Web.Services.WebService {  [WebMethod]   public string CalcPi(int digits) {     StringBuilder pi = new StringBuilder("3", digits + 2);     // Way-cool fast pi calculator running on a huge processor...     return pi.ToString();   } } 

Now imagine a version of the CalcPi program that used 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). Although the underlying protocol of Web services is HTTP- and XML-based and we could form a Web service request fairly readily to ask for the digits of pi we're after, it's simpler to let VS.NET generate a class to make the Web services calls for you. You can do this in the Project menu using the Add Web Reference item. The Add Web Reference dialog allows you to enter the URL of the WSDL (Web Service Description Language) that describes the Web service you'd like to call. For example, after installing the Web service sample that accompanies this book, [5] you can access the WSDL via the following URL:

[5] See the ReadMe.htm file that comes with the book's samples for detailed instructions on setting them up.

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

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

 namespace AsyncCalcPi.localhost {   [WebServiceBindingAttribute(Name="CalcPiServiceSoap", ...)]   public class CalcPiService : SoapHttpClientProtocol {     public CalcPiService() {       this.Url = "http://localhost/CalcPiWebService/CalcPiService.asmx";     }     [SoapDocumentMethodAttribute("http://tempuri.org/CalcPi", ...)]  public string CalcPi(int digits) {...}   public IAsyncResult   BeginCalcPi(   int digits,   System.AsyncCallback callback,   object asyncState) {...}   public string EndCalcPi(IAsyncResult asyncResult) {...}  } } 

Calling the Web service is now a matter of creating an instance of the proxy and calling the method you're interested in:

  localhost.CalcPiService service = new localhost.CalcPiService();  void calcButton_Click(object sender, System.EventArgs e) {   piTextBox.Text = service.CalcPi((int)digitsUpDown.Value); } 

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 BeginXxx/EndXxx method pairs, 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 BeginCalcPi method:

 enum CalcState {   Pending,   Calculating,   Canceled, } CalcState state = CalcState.Pending;  localhost.CalcPiService service = new localhost.CalcPiService();  void calcButton_Click(object sender, System.EventArgs e) {   switch( state ) {     // Start a new calculation     case CalcState.Pending:       // Allow canceling       state = CalcState.Calculating;       calcButton.Text = "Cancel";  // Start Web service request   service.BeginCalcPi(   (int)digitsUpDown.Value,   new AsyncCallback(PiCalculated),   null);  break;     // Cancel a running calculation     case CalcState.Calculating:       state = CalcState.Canceled;       calcButton.Enabled = false;  service.Abort(); // Fail all outstanding requests  break;     // Shouldn't be able to press Calc button while it's canceling     case CalcState.Canceled:       Debug.Assert(false);       break;   } }  void PiCalculated(IAsyncResult res) {...}  

The BeginCalcPi method takes the method parameters and an instance of an AsyncCallback delegate. The application provides the PiCalculated event handler to match the AsyncCallback signature. The PiCalculated method, which will be called when the Web service returns, is responsible for harvesting results.

Also, even though there is no way to get progress from a Web service (all the more reason to call it asynchronously), calls to a Web service can be canceled by a call to the Abort method. Be careful with this one, however, because it will cancel all outstanding requests, not just a specific one.

When the AsyncCallback event handler is called, it means that the Web service has returned something:

 void PiCalculated(IAsyncResult res) {  try {  ShowPi(  service.EndCalcPi(res)  );  }   catch( WebException ex ) {  ShowPi(ex.Message);  }  } 

The call to EndCalcPi, passing in the IAsyncResult parameter, provides any results of calling the Web service. If the Web service call was successful, the return value is pi. If, on the other hand, the Web service call was unsuccessful , because it timed out or was canceled, EndCalcPi will throw a WebException. That's why PiCalculated wraps the call to EndCalcPi in a try-catch block. ShowPi will be called on to show either the digits of pi that were calculated by the Web service or the exception message (the result of a canceled Web service call is shown in Figure 14.7).

Figure 14.7. The Result of a Canceled Call to the Pi Web Service

The ShowPi method displays the results of the Web service invocation:

  delegate void ShowPiDelegate(string pi);  void ShowPi(string pi) {   if( this.InvokeRequired == false ) {     piTextBox.Text = pi;     state = CalcState.Pending;     calcButton.Text = "Calc";     calcButton.Enabled = true;   }   else {     ShowPiDelegate showPi = new ShowPiDelegate(ShowPi);     this.BeginInvoke(showPi, new object[] {pi});   } } 

Notice that the ShowPi method contains the check to see whether an invoke is required, and it calls Control.BeginInvoke to transfer from the current thread to the UI thread, just as we've been doing for most of this chapter. This is necessary because the Web service completion notification will come in on a thread other than the UI thread.



Windows Forms Programming in C#
Windows Forms Programming in C#
ISBN: 0321116208
EAN: 2147483647
Year: 2003
Pages: 136
Authors: Chris Sells

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