December 18, 2014
Hot Topics:

Working With Asynchronous .NET Web Service Clients

  • September 16, 2004
  • By Kenn Scribner
  • Send Email »
  • More Articles »

Show Me the Code

I created two .NET projects for this article. One is a basic Web Service; the other is a Windows Forms client that calls the Web Service. The Web Service is a simple service that maintains a list of people and their phone numbers. It reads the information from a file and caches the file contents in the ASP.NET application cache for efficiency. The Web Method I call is named FindPhoneNumberForName.

The client is a Windows Forms application in this demonstration, but this isn't universally the case. Quite often, the client could be another service or perhaps some business logic in a large enterprise application. Any .NET consumer of a Web Service can use the techniques I show here. Figure 2 shows the user interface I created.

Figure 2: Demonstration User Interface

The user provides a name for which he or she desires a phone number, clicks "Get", and then waits for the response.

The interesting code is in the button click handler for the "Get" button:

private void cmdGet_Click(object sender, System.EventArgs e)
{
   // Create the Web Service proxy...
   YPServiceClass ws = new YPServiceClass();

   if ( tbName.Text.Length > 0 )
   {
      this.Cursor = Cursors.AppStarting;
      try
      {
         // Make the asynchronous call
         ws.BeginFindPhoneNumberForName(tbName.Text,
            new AsyncCallback(lookupHandler),ws);
      } // try
      catch (Exception ex)
      {
         string strMsg =
            String.Format("Error initiating asynchronous " +
                          "call: '{0}'",ex.Message);
         MessageBox.Show(strMsg, "Web Error",
                         MessageBoxButtons.OK,
                         MessageBoxIcon.Error);
         this.Cursor = Cursors.Arrow;
      } // catch
       // if
   else
   {
      lblPhone.Text = "";
   } // else
}

I italicized the actual call to the Web Service. Note that unlike a synchronous call, which would look like this:

ws.FindPhoneNumberForName(tbName.Text);

it has a very different invocation that looks like this:

ws.BeginFindPhoneNumberForName(tbName.Text,
     new AsyncCallback(lookupHandler),ws);

You know it's an asynchronous call because I use the "begin" form of the method's signature, but other parameters go into the request also. The first is an instance of an AsyncCallback object, and the second is my instance of the Web Service proxy itself. The AsyncCallback object is actually a .NET delegate defined specifically for asynchronous callbacks. It defines the method signature for a method that you provide to handle the returned information from the Web Service. The delegate defines a method signature like the following:

delegate void AsyncCallback(IASyncResult iar);

If you generate a method with the preceding signature and provide that to the constructor of the AsyncCallback delegate (as I did in the Web Service invocation), when the Web Service response arrives from the remote server, .NET will invoke the method you provided to the delegate's constructor. In this case, .NET will invoke the method I named lookupHandler.

The lookupHandler method is then where I actually strip out the results of the Web Service invocation:

private void lookupHandler(IAsyncResult iar)
{
   try
   {
      // Finalize the asynchronous call
      string number = ((YPServiceClass)iar.AsyncState).
         EndFindPhoneNumberForName(iar);

      // Update the user interface
      object[] args = new object[1];
      args[0] = number;
      this.Invoke(new LookupUIUpdateHandler(lookupUIUpdateHandler),
         args);
   } // try
   catch (Exception ex)
   {
      string strMsg = 
         String.Format("Error terminating asynchronous " + 
         "call: '{0}'",ex.Message);
      MessageBox.Show(strMsg,"Web Error",
                      MessageBoxButtons.OK,
                      MessageBoxIcon.Error);
   } // catch
}

I italicized the magic line of code in lookupHandler for extracting the resulting Web Service return parameter information, but I'll repeat it here because it looks a little strange:

string number =
   ((YPServiceClass)iar.AsyncState).EndFindPhoneNumberForName(iar);

As you may recall, when we invoked the Web Service asynchronously, we passed our parameters, an AsyncCallback object, and a reference to the Web Service proxy itself into the "begin" method. We provided the proxy reference to a call implemented by the proxy itself because an object must be responsible for maintaining the state of the asynchronous call. In other words, some object .NET can control must contain the results of the call so that .NET can forward those results to us. This is the job of the proxy: the proxy implements the "end" (call finalization) method. If you write your own asynchronous calls, you may have to provide your own asynchronous state object. But in the case of .NET Web Services and its proxies, you use the proxy objects themselves.

So, the following piece of code does nothing more than return the original proxy object:

((YPServiceClass)iar.AsyncState

And becaise it's our Web Service's proxy, it implements the "end" method, EndFindPhoneNumberForName. So, all we really did was perform a simple cast to obtain our proxy and then finalize the call. The return value for EndFindPhoneNumberForName is simply the return value of the phone number request method, which is a string representing the phone number of the individual requested.

If I were calling this Web Service code and didn't need to display the string in a window, I'd be done. But in the lookupHandler method, some additional code is necessary because I am sending this string to a window for display. Remember, only the thread that created the window can change its state, and when .NET calls back with the result of an asynchronous Web Service call, the callback is executing on a .NET thread pool thread. So, I must marshal the string from the .NET thread pool thread to my application's user interface thread before I can display it. If I don't, the application might not work. The only correct thing to do is marshal the data, even if it is just a simple string.

As I mentioned previously, .NET accomplishes marshaling by its eventing environment and the Control.Invoke method. The following code sets up the call to Invoke:

object[] args = new object[1];
args[0] = number;
this.Invoke(new LookupUIUpdateHandler(lookupUIUpdateHandler), args);

The LookupUIUpdateHandler follows the .NET guidelines for triggering Invoke events. It defines a method that accepts the type and number of parameters contained within args. For this example, that's merely a single string input:

delegate void LookupUIUpdateHandler(string number);

To the delegate's constructor, I passed a reference to the method that implements the delegate, lookupUIUpdateHandler:

private void lookupUIUpdateHandler(string number)
{
   // Update the window
   lblPhone.Text = number != null ? number : "(no number listed)";

   // Reset the cursor (previously set when async call
   // was made).
   this.Cursor = Cursors.Arrow;
}

And, it is here I finally update the user interface because .NET guarantees me that at this point I'll be on the proper thread for managing windows in my application.

Wrap Up

And there you have it: .NET provides a pool of threads that you can use for asynchronous client-side Web Service processing. The way you get to these threads is to use the special "begin" and "end" methods .NET generates when it reads the service description language. You'll find these methods in the resulting proxy file created when adding a Web reference to your project.

By using one of these internal .NET threads, you can free your primary process thread (the one that most likely created your user interface if you're calling the Web Service from a Windows Forms application). The secondary thread blocks while waiting for the Web Request to return, allowing the primary thread to continue performing its main processing tasks (such things as painting, animation, and button/text inputs). This is the essence of asynchronous processing, and it's quite effective. Give it a try!

About the Author

Kenn Scribner is with Wintellect.





Page 2 of 2



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Sitemap | Contact Us

Rocket Fuel