Chapter 15. Consuming XML Web Services Asynchronously

In this chapter, you will learn how to:

  • Use asynchronous communications to invoke XML Web services operations.

  • Use callback methods to process the results from asynchronous XML Web ­service operations.

  • Use .NET synchronization mechanisms to process the results from asynchronous XML Web service operations.

So far, our XML Web service client applications have called XML Web service methods and then waited for a response, blocking application processing until the Web method returns. Known as synchronous communications, this approach is the most common and easily understood way to consume XML Web services. However, sometimes you might want your client application to continue processing other tasks or have the application allow user interaction while it waits for the response from the XML Web service. Asynchronous communications allow you to call an XML Web service method and have control return to your program immediately. Your program is free to undertake other tasks, including making more asynchronous requests, and then come back to handle the XML Web service response after the asynchronous operation has been completed. The following diagrams compare synchronous and asynchronous communications, illustrating the difference in communications flow.

In the first diagram, the client must wait for the XML Web service to respond before it can continue processing.

Consuming XML Web Services Asynchronously

Using asynchronous communication, the client doesn’t have to wait for a response from the XML Web service. The client can process other tasks and ­initiate additional asynchronous requests before coming back to process the responses from the XML Web service.

Consuming XML Web Services Asynchronously

Asynchronous communication is a powerful tool that gives you great control over the execution of your application; however, you should avoid using it unnecessarily. Asynchronous communications make your program more complicated and difficult to debug. In addition, asynchronous requests will not necessarily return in the order in which they were submitted. This behavior can allow your application to perform actions out of sequence; used inappropriately, asynchronous requests can make your program behave strangely and confuse its users.

Asynchronous XML Web Services

The features necessary to communicate asynchronously with XML Web services are implemented entirely on the client side. You do not need to do anything special to enable your XML Web service to support asynchronous communications. Therefore, all of the XML Web services we have developed so far will work with clients that use asynchronous communications.

Slowing Down the Test XML Web Service

Although XML Web services support asynchronous communications without modification, we are going to modify our ValidatorService example to make it easier to test our asynchronous client applications. Because all of the examples we’ve developed earlier have involved a client and service operating on the same machine, network latency is minimal. Therefore, to see the effects of our new asynchronous client, we need to slow down our service to mimic slow networks and busy servers. We will introduce a random length delay of between one and three seconds during ValidateCard Web method calls. With this delay mechanism in place, responses to credit card validation requests will not return immediately and will not necessarily return in the same order in which you submit them. As a result, we’ll be able to see the results of using asynchronous calls from clients more clearly.

Procedure 15-1. Copy the ValidatorService XML Web Service Project

  1. Using the Copy Project function in Visual Studio .NET, copy the ValidatorService XML Web service that you created in the section "The Global.asax file" in Chapter 12. For this example, copy the ValidatorService project to http://localhost/XMLWebServices/Chapter15/AsyncComms/ValidatorService/.

  2. Close the old project and open the new one.

  3. Save the new solution files by selecting Save All from the File menu and choosing a suitable location.

Procedure 15-2. Modify the ValidatorService XML Web Service

  1. In Solution Explorer, right-click the Validation.asmx file and select View Code from the shortcut menu. This will open Validation.asmx.cs (if you are using C#) or Validation.asmx.vb (if you are using Visual Basic .NET).

  2. Insert the statements to import the System.Threading namespace at the top of the Validation.asmx code-behind file. Importing this namespace gives the code access to the Thread class, which we use later to introduce a delay before processing a credit card validation request.

    Example 15-1. C#

    // the following statement allows us to
    // use the Thread class
    using System.Threading;

    Example 15-2. Visual Basic .NET

    ‘ the following statement allows us to
    ‘ use the Thread class
    Imports System.Threading
  3. Locate the ValidateCard method and modify it to pause for between 1000 and 3000 milliseconds before processing a credit card validation request. We highlight the required modification to the code in bold below.

    Example 15-3. C#

    [WebMethod(EnableSession=true)]
    public void ValidateCard(ref ValidationObject p_object) {
        // delay the validation request between 1 and 3 
        // seconds using a new instance of the Random 
        // class to generate a value between 1000 and 3000
        // and causing the active thread to sleep for 
        // that number of milliseconds.
        Thread.Sleep((new Random()).Next(1000,3000));
    
        // define a boolean to hold the result
        // of the validation request.
        bool x_result = false;
    
        // define a boolean to indicate if the
        // validation request resulted in an
        // exception being thrown by the 
        // CreditCardValidator.dll library
        bool x_error = false;
    
        try {
            // create the validator instance that we will
            // use to check the card number
            Validator x_validator = new Validator();
    
            // use the type of card supplied in the 
            // validation object to determine which
            // library method we should call in order
            // to perform the validation.
            switch (p_object.o_card_type) {
                case CARD_TYPE.AMEX:
                    // the client has requested that we validate
                    // an AMEX card number
                    x_result 
                        = x_validator.ValidateAMEX(
                            p_object.o_card_number);
                    break;
                case CARD_TYPE.MASTERCARD:
                    // the client has requested that we validate
                    // a MasterCard card number
                    x_result
                        = x_validator.ValidateMasterCard(
                            p_object.o_card_number);
                    break;
                case CARD_TYPE.VISA:
                    // the client has requested that we validate
                    // a VISA card number
                    x_result
                        = x_validator.ValidateVisa(
                            p_object.o_card_number);
                    break;
                default:
                    // the method should not reach this point; if it
                    // does, then it is because the client has specified
                    // a value which is not contained in the CARD_TYPE
                    // enumeration. Should this happen, the client has 
                    // requested that we validate a card type that we 
                    // do not support.
                    x_result = false;
                    break;
            }
        } catch (ApplicationException x_ex) {
            // update the variable to indicate 
            // that an exception has been thrown by
            // CreditCardValidator.dll library
            x_error = true;
    
            // return a soap exception with a custom fault 
            // code based on  the type of the application 
            // exception
            throw GetException(x_ex);
        } finally {
            // get the status object for this client
            ClientStats x_stats = GetClientStatistics();
            // increment the counter for the total
            // number of requests
            x_stats.o_total_requests++;
            // if the result was not true, or if an
            // exception was thrown, then update the
            // failed request counter
            if (x_error || !x_result) {
                x_stats.o_failed_validations++;
            }
    
            // set the result status in the validation object
            p_object.o_valid = x_result;
    
            // update the array list held in the application
            // state to reflect the new request
            ArrayList x_list 
                = (ArrayList)Application["ValidationObjects"];
    //      if (x_list == null) {
    //          x_list = new ArrayList();
    //          Application["ValidationObjects"] = x_list;
    //      }
            x_list.Add(p_object);
        }
    }

    Example 15-4. Visual Basic .NET

    <WebMethod(EnableSession:=True)> _
    Public Sub ValidateCard(ByRef p_object As ValidationObject)
        ‘ delay the validation request between 1 and 3 
        ‘ seconds using a new instance of the Random 
        ‘ class to generate a value between 1000 and 3000
        ‘ and causing the active thread to sleep for 
        ‘ that number of milliseconds.
        Thread.Sleep((New Random()).Next(1000,3000))
    
        ‘ define a boolean to hold the result
        ‘ of the validation request.
        Dim x_result As Boolean
    
        ‘ define a boolean to indicate if the
        ‘ validation request resulted in an
        ‘ exception being thrown by the 
        ‘ CreditCardValidator.dll library
        Dim x_error As Boolean = False
    
        Try
            ‘ create the validator instance that we will
            ‘ use to check the card number
            Dim x_validator As Validator = New Validator()
    
            ‘ use the type of card supplied in the 
            ‘ validation object to determine which
            ‘ library method we should call in order
            ‘ to perform the validation.
            Select Case p_object.o_card_type
                Case CARD_TYPE.AMEX
                    ‘ the client has requested that we validate
                    ‘ an AMEX card number
                    x_result _
                        = x_validator.ValidateAMEX( _
                            p_object.o_card_number)
                Case CARD_TYPE.MASTERCARD
                    ‘ the client has requested that we validate
                    ‘ a MasterCard card number
                    x_result _
                        = x_validator.ValidateMasterCard( _
                            p_object.o_card_number)
                Case CARD_TYPE.VISA
                    ‘ the client has requested that we validate
                    ‘ a VISA card number
                    x_result _
                        = x_validator.ValidateVisa( _
                            p_object.o_card_number)
                Case Else
                    ‘ the method should not reach this point; if it
                    ‘ does, then it is because the client has specified
                    ‘ a value which is not contained in the CARD_TYPE
                    ‘ enumeration. Should this happen, the client has 
                    ‘ requested that we validate a card type that we 
                    ‘ do not support.
                    x_result = False
            End Select
        Catch x_ex As ApplicationException
            ‘ update the variable to indicate 
            ‘ that an exception has been thrown by
            ‘ CreditCardValidator.dll library
            x_error = True
    
            ‘ return a soap exception with a custom fault 
            ‘ code based on  the type of the application 
            ‘ exception
            Throw GetException(x_ex)
        Finally
            ‘ get the status object for this client
            Dim x_stats As ClientStats = GetClientStatistics()
            ‘ increment the counter for the total
            ‘ number of requests
            x_stats.o_total_requests = x_stats.o_total_requests + 1
    
            ‘ if the result was not true, or if an
            ‘ exception was thrown, then update the
            ‘ failed request counter
            If x_error Or Not x_result Then
                x_stats.o_failed_validations _
                    = x_stats.o_failed_validations + 1
            End If
    
            ‘ set the result status in the validation object
            p_object.o_valid = x_result
    
            Dim x_list As ArrayList _
                = CType(Application("ValidationObjects"), ArrayList)
            ‘ check to ensure that we have a list to work with - if the
            ‘ value returned from the Application object is null, then 
            ‘ this is the first request made to the XML Web service 
            ‘ method, and we should create the list for use by future 
            ‘ calls
            ‘If x_list Is Nothing Then
            ‘    x_list = New ArrayList()
            ‘    Application("ValidationObjects") = x_list
            ‘End If
            ‘ update the array list held in the application
            ‘ state to reflect the new request
            x_list.Add(p_object)
    
        End Try
    End Sub
  4. To build the project, choose Build Solution from the Build menu (or press Ctrl+Shift+B).

Asynchronous XML Web Service Clients

.NET proxy classes contain all of the features you require to communicate asynchronously with XML Web services; however, asynchronous communications still place more responsibility on you. You must learn new techniques to handle asynchronous communications within your client application. Later in this section, we discuss four different approaches for communicating with XML Web services asynchronously, but first you need to understand some things common to all of these approaches.

Proxy Class Methods

When we discussed the creation of proxy classes in Chapter 4, we mentioned that the proxy contains three methods for each available XML Web service method. The names of the three proxy methods always follow the same pattern: WebMethodName, BeginWebMethodName, and EndWebMethodName. In the case of our XML Web service method named ValidateCard, the proxy class contains the following methods:

  • ValidateCard

  • BeginValidateCard

  • EndValidateCard

We discuss each of these methods and the role they play in asynchronous communications in the following sections.

The ValidateCard Method

The ValidateCard method provides synchronous communications with the XML Web service and is the one that we have used in all of our examples so far. When you call the ValidateCard method, the proxy invokes the XML Web ­service method and blocks further execution until the service responds.

The BeginValidateCard Method

When you call the BeginValidateCard method, the proxy invokes the XML Web service method but returns control to your program immediately, without waiting for a response. The BeginValidateCard method takes two arguments in addition to those specified by the Web method definition and always returns a System.IAsyncResult instance. To illustrate these differences, compare the method definitions of the ValidateCard and BeginValidateCard methods from the ValidatorService proxy class. The differences are highlighted in bold.

Example 15-5. C#

public void ValidateCard(ref ValidationObject p_object) {

public System.IAsyncResult 
    BeginValidateCard(ValidationObject p_object,
    System.AsyncCallback callbackobject asyncState) {

Example 15-6. Visual Basic .NET

Public Sub ValidateCard(ByRef p_object As ValidationObject)

Public Function _
    BeginValidateCard(ByVal p_object As ValidationObject, _
    ByVal callback As System.AsyncCallback, _
    ByVal asyncState As Object) As System.IAsyncResult

The ValidationObject argument that we pass by reference to the ValidateCard method is passed by value to the BeginValidateCard method. The BeginValidateCard method cannot update the contents of the ValidationObject instance because the result of the credit card validation request is not available when the BeginValidateCard method returns; therefore, there is no benefit in passing the ValidationObject by reference. The values of any by-reference arguments are available when you call EndValidateCard, discussed in the next section.

The IAsyncResult returned by the BeginValidateCard method is very important for processing asynchronous XML Web service operations. The response to an asynchronous request is received an indeterminate amount of time after the request was initiated and is very likely to be handled by a completely different section of code than that which submitted the request. Because you can have multiple asynchronous requests outstanding concurrently, you need some way of mapping a response back to the original request; this is the purpose of the IAsyncResult return value. In addition, the properties of IAsyncResult provide access to useful information about the state of an outstanding asynchronous operation. We will explore the use of the IAsyncResult return value as well as the new callback and asyncState arguments more fully as we develop our asynchronous client examples later in this chapter.

The EndValidateCard Method

The EndValidateCard method obtains the result of the asynchronous XML Web method previously invoked using the BeginValidateCard method. The EndValidateCard method takes an IAsyncResult argument that identifies the asynchronous operation for which you want to process the results. This is the IAsyncResult returned by the BeginValidateCard method. Other than the IAsyncResult argument, the signature and return values of the EndValidateCard method are the same as those of the synchronous ValidateCard method. To illustrate these similarities, compare the method definitions of the ValidateCard and EndValidateCard methods from the ValidatorService proxy class. The differences appear in bold.

Example 15-7. C#

public void ValidateCard(ref ValidationObject p_object) {

public void 
    EndValidateCard(System.IAsyncResult asyncResult,
    out ValidationObject p_object) {

Example 15-8. Visual Basic .NET

Public Sub ValidateCard(ByRef p_object As ValidationObject)

Public Sub _
    EndValidateCard(ByVal asyncResult As System.IAsyncResult, _
    ByRef p_object As ValidationObject)

The return type of the EndValidateCard method is the same as that of the ValidateCard method and contains the XML Web service method result (void in this case, as we are using a reference argument instead). Because the ValidateCard method takes a ValidationObject reference argument, the EndValidateCard method signature includes a ValidationObject modified with the out modifier in C# and the ByRef modifier in Visual Basic .NET. This additional ValidationObject argument gives you access to the credit card validation results that we previously accessed through the ValidationObject reference argument of the ValidateCard method.

Important

When called, the EndValidateCard method will throw any exceptions raised by the XML Web service.

Using Callbacks to Signal Asynchronous Method Completion

We will first demonstrate the use of callback methods to access an XML Web service asynchronously. Callback methods are a common asynchronous programming practice, and in our case they work as follows.

  1. We call BeginValidateCard, passing a reference to a method to call when the asynchronous operation completes. This is the callback method.

  2. Control returns immediately to our program so that it can continue performing other tasks.

  3. Some time later, when the asynchronous operation completes, the proxy invokes the callback method to process the results of the asynchronous operation. The callback method executes in a separate thread so that general program execution can continue without interruption.

To demonstrate the use of callbacks, we will create a Windows Forms client application to perform VISA card validation against the ValidatorService XML Web service that we developed earlier in this chapter. This client application is similar to those we have developed in previous chapters. However, because we are communicating asynchronously with the XML Web service, we have modified the interface to allow you to enter up to three VISA card numbers, as shown in this screenshot.

Using Callbacks to Signal Asynchronous Method Completion

When you click the Validate button, the application submits each credit card number to the ValidatorService for validation asynchronously, displaying the result as the callback method receives the XML Web service response and processes it. Because of the random delay we built into the ValidateCard method, the responses will most likely not return in the sequence we submit them.

To enable callbacks, we use the additional arguments of the BeginValidateCard method that we introduced earlier. To refresh your memory, here is the BeginValidateCard declaration.

Example 15-9. C#

public System.IAsyncResult 
    BeginValidateCard(ValidationObject p_object,
    System.AsyncCallback callbackobject asyncState) {

Example 15-10. Visual Basic .NET

Public Function _
    BeginValidateCard(ByVal p_object As ValidationObject, _
    ByVal callback As System.AsyncCallback, _
    ByVal asyncState As Object) As System.IAsyncResult 

The callback argument identifies the callback method to execute when the ­asynchronous operation completes. The .NET Framework uses delegates as a mechanism to pass method references, in this case the System.AsyncCallback delegate. The method referenced by the AsyncCallback delegate must take a System.IAsyncResult argument and return void. When the asynchronous operation completes, the proxy will call the callback method and pass it the IAsync­Result associated with the asynchronous operation that has just completed. In our example, we will use the following callback method declaration.

Example 15-11. C#

private void ValidationCallback(IAsyncResult p_asyncResult) {

Example 15-12. Visual Basic .NET

Private Sub ValidationCallback(ByVal p_asyncResult As IAsyncResult)

The asyncState argument of the BeginValidateCard method takes a reference to any object. The proxy does not use the asyncState object but instead stores the value in the AsyncState property of the IAsyncResult associated with the asynchronous operation (the one returned by BeginValidateCard). Using the asyncState argument allows you to associate relevant information with a request for easy retrieval by the callback method via its IAsyncResult argument. We will use the asyncState argument to pass a reference to the user interface Label control in which the credit card validation result should be displayed; this gives us a simple way to map the asynchronous response to the appropriate label.

Procedure 15-3. Create the WindowsFormsClient project

  1. Create a new Visual Studio .NET project using either the C# or Visual Basic .NET Windows Application project template. In the New Project dialog box, name the project WindowsFormsClient and specify C:XMLWebServicesSBSProjectsChapter15Callback for the location.

    Create the WindowsFormsClient project
  2. Create a new Web reference that points to the ValidatorService that we created earlier in this chapter. The Web reference URL is http://localhost/XMLWebServices/Chapter15/AsyncComms/ValidatorService/Validation.asmx.

  3. Rename the new Web reference Validator by either selecting it in Solution Explorer and pressing F2 or by right-clicking it and selecting Rename from the shortcut menu.

Procedure 15-4. Create the User Interface

  1. In Solution Explorer, double-click on the source file for Form1 (either Form1.cs or Form1.vb depending on which language you are using); this opens the design view for Form1.

  2. Open the Properties window for Form1 either by pressing F4 or by selecting Properties Window from the View menu. Configure the properties as specified in the following table.

    Property

    Value

    Text

    Asynchronous VISA Validator Client

    Size

    480,220

  3. Using the Visual Studio .NET toolbox, add the controls listed in the following table to Form1 and configure their properties as specified. We want the form to look like the following screenshot.

    Create the User Interface

    Control

    Property

    Value

    Label

    Location

    8, 20

     

    Text

    Card Number 1:

     

    TextAlign

    MiddleRight

    TextBox

    Name

    CardNumber1

     

    Location

    120, 20

     

    Size

    130, 20

     

    Text

    Delete the contents of the Text ­property value.

    Label

    Location

    260, 20

     

    Size

    50, 23

     

    Text

    Result:

     

    TextAlign

    MiddleRight

    Label

    Name

    Result1

     

    Location

    330, 20

     

    Size

    120, 23

     

    Text

    Delete the contents of the Text ­property value.

     

    TextAlign

    MiddleLeft

    Label

    Location

    8, 60

     

    Text

    Card Number 2:

     

    TextAlign

    MiddleRight

    TextBox

    Name

    CardNumber2

     

    Location

    120, 60

     

    Size

    130, 20

     

    Text

    Delete the contents of the Text ­property value.

    Label

    Location

    260, 60

     

    Size

    50, 23

     

    Text

    Result:

     

    TextAlign

    MiddleRight

    Label

    Name

    Result2

     

    Location

    330, 60

     

    Size

    120, 23

     

    Text

    Delete the contents of the Text ­property value.

     

    TextAlign

    MiddleLeft

    Label

    Location

    8, 100

     

    Text

    Card Number 3:

     

    TextAlign

    MiddleRight

    TextBox

    Name

    CardNumber3

     

    Location

    120, 100

     

    Size

    130, 20

     

    Text

    Delete the contents of the Text ­property value.

    Label

    Location

    260, 100

     

    Size

    50, 23

     

    Text

    Result:

     

    TextAlign

    MiddleRight

    Label

    Name

    Result3

     

    Location

    330, 100

     

    Size

    120, 23

     

    Text

    Delete the contents of the Text ­property value.

     

    TextAlign

    MiddleLeft

    Button

    Name

    ValidateButton

     

    Location

    200, 150

     

    Text

    Validate

Procedure 15-5. Implement the WindowsFormsClient Functionality

  1. From the Form1 design view window, double-click on the Validate button. This will bring up the code view of Form1 and show an empty ValidateButton_Click method.

  2. Replace the empty ValidateButton_Click method with the following code.

    When the validate button is clicked, we create and configure a ValidationObject for each VISA card number entered. We then call the BeginValidateCard method of the proxy class, passing it the ValidationObject and an AsyncCallback delegate instance referencing the ValidationCallback method. As the final (asyncState) argument, we pass a reference to the Label control on Form1 in which the validation result should be displayed. This use of the asyncState removes the need for the callback method to figure out which validation response it is dealing with and map it to the credit card number.

    Example 15-13. C#

    private void ValidateButton_Click(object sender, 
        System.EventArgs e) {
    
        // create a delegate instance for the callback method
        AsyncCallback x_callback = 
            new AsyncCallback(ValidationCallback);
    
        // for the purpose of this example, it helps if we clear the 
        // Result labels before we submit the validation requests.
        Result1.Text = "";
        Result2.Text = "";
        Result3.Text = "";
    
        // create and configure a ValidationObject for each card number
        // entered and asynchronously request validation from
        // the XML Web service via the proxy class. use the last
        // argument to pass a reference to the Result label in which
        // the result of the validation request should be displayed.
        if (CardNumber1.Text != "") {
            // create the ValidationObject that we will send to 
            // the XML Web service
            ValidationObject x_object = new ValidationObject();
    
            // assume the credit card is a VISA card and set the 
            // card type for the validation object.
            x_object.o_card_type = CARD_TYPE.VISA;
    
            // set the VISA card number
            x_object.o_card_number = CardNumber1.Text;
    
            // submit the request
            o_validator.BeginValidateCard(x_object, 
                x_callback, Result1);
        }
    
        // do the same for CardNumber2 
        if (CardNumber2.Text != "") {
            ValidationObject x_object = new ValidationObject();
            x_object.o_card_type = CARD_TYPE.VISA;
            x_object.o_card_number = CardNumber2.Text;
            o_validator.BeginValidateCard(x_object, 
                x_callback, Result2);
        }
    
        // do the same for CardNumber3
        if (CardNumber3.Text != "") {
            ValidationObject x_object = new ValidationObject();
            x_object.o_card_type = CARD_TYPE.VISA;
            x_object.o_card_number = CardNumber3.Text;
            o_validator.BeginValidateCard(x_object, 
                x_callback, Result3);
        }
    }

    Example 15-14. Visual Basic .NET

    Private  Sub ValidateButton_Click(ByVal sender As Object, _
        ByVal e As System.EventArgs) Handles ValidateButton.Click
    
        ‘ create a delegate instance for the callback method
        Dim x_callback As AsyncCallback = _
            AddressOf Me.ValidationCallback
    
        ‘ for the purpose of this example, it helps if we clear the 
        ‘ Result labels before we submit the validation requests.
        Result1.Text = ""
        Result2.Text = ""
        Result3.Text = ""
    
        ‘ create and configure a ValidationObject for each card number
        ‘ entered and asynchronously request validation from
        ‘ the XML Web service via the proxy class. use the last
        ‘ argument to pass a reference to the Result label in which
        ‘ the result of the validation request should be displayed.
        If CardNumber1.Text <> "" Then
            ‘ create the ValidationObject that we will send to 
            ‘ the XML Web service
            Dim x_object As ValidationObject =  New ValidationObject()
     
            ‘ assume the credit card is a VISA card and set the 
            ‘ card type for the validation object.
            x_object.o_card_type = CARD_TYPE.VISA
     
            ‘ set the VISA card number
            x_object.o_card_number = CardNumber1.Text
     
            ‘ submit the request
            o_validator.BeginValidateCard(x_object, _
                x_callback, Result1)
        End If
     
        ‘ do the same for CardNumber2 
        If CardNumber2.Text <> "" Then
            Dim x_object As ValidationObject =  New ValidationObject()
            x_object.o_card_type = CARD_TYPE.VISA
            x_object.o_card_number = CardNumber2.Text
            o_validator.BeginValidateCard(x_object, _
                x_callback, Result2)
        End If
     
        ‘ do the same for CardNumber3
        If CardNumber3.Text <> "" Then
            Dim x_object As ValidationObject =  New ValidationObject()
            x_object.o_card_type = CARD_TYPE.VISA
            x_object.o_card_number = CardNumber3.Text
            o_validator.BeginValidateCard(x_object, _
                x_callback, Result3)
        End If
    End Sub
  3. Scroll to the top of the Form1 code view window, and then add the statements to import the proxy class namespace.

    Example 15-15. C#

    // import the proxy class
    using WindowsFormsClient.Validator;

    Example 15-16. Visual Basic .NET

    ‘ import the proxy class
    Imports WindowsFormsClient.Validator
  4. Find the Form1 class declaration, and immediately below it add a declaration for the proxy. The lines you need to add are highlighted in bold.

    Because we use the IAsyncResult instances to map BeginValidateCard and EndValidateCard method calls in different methods, we need to use the same proxy class instance for both method calls. Therefore, we need to make the proxy object a member of the class.

    Example 15-17. C#

    public class Form1 : System.Windows.Forms.Form
    {
        // declare and create an instance of the ValidatorService
        // proxy class.
        private ValidatorService o_validator = 
            new ValidatorService();

    Example 15-18. Visual Basic .NET

    Public Class Form1
        Inherits System.Windows.Forms.Form
        ‘ declare and create an instance of the ValidatorService
        ‘ proxy class.
        Private o_validator As ValidatorService =  _
            New ValidatorService()
  5. Add the new ValidationCallback method.

    The ValidationCallback method is called automatically when an asynchronous operation is completed. It uses the IAsyncResult instance it receives as an argument to the EndValidateCard method. The EndValidateCard either throws an exception indicating an invalid card number or provides an updated ValidationObject. The appropriate result message is determined and displayed in the Form1 label referenced by the IAsyncResult.AsyncState property.

    Example 15-19. C#

    // Callback method for processing the result of 
    // asynchronous BeginValidateCard calls 
    private void ValidationCallback(IAsyncResult p_asyncResult) {
    
        // create a ValidationObject variable to hold the 
        // result of the validation request.
        ValidationObject x_result;
    
        // a reference to the Result label in which to display
        // the validation result was passed as the asyncState 
        // reference to the BeginValidateCard method. We retrieve
        // this through the IAsyncResult.AsyncState property so that
        // we can easily determine where to display the validation 
        // result.
        Label Result = (Label)p_asyncResult.AsyncState;
    
        // call the EndValidate method to get the result of
        // the validation request.
        try {
            o_validator.EndValidateCard(p_asyncResult, out x_result);
    
            if (x_result.o_valid) {
                Result.Text = "Number Valid";
            } else {
                Result.Text = "Number Invalid";
            }
        } catch (System.Web.Services.Protocols.SoapException x_ex) {
            // display the content of the exception
            switch (x_ex.Code.ToString()) {
                case "Client.IllegalCharacter":
                    Result.Text = "Illegal Character";
                    break;
                case "Client.InvalidLength":
                    Result.Text = "Invalid Length";
                    break;
                case "Client.InvalidPrefix":
                    Result.Text = "Invalid Prefix";
                    break;
                default:
                    Result.Text = "Unexpected Error";
                    break;
            }
        }
    }

    Example 15-20. Visual Basic .NET

    Private Sub ValidationCallback(ByVal p_asyncResult As IAsyncResult)
    
        ‘ create a ValidationObject variable to hold the 
        ‘ result of the validation request.
        Dim x_result As ValidationObject
    
        ‘ a reference to the Result label in which to display
        ‘ the validation result was passed as the asyncState 
        ‘ reference to the BeginValidateCard method. We retrieve
        ‘ this through the IAsyncResult.AsyncState property so that
        ‘ we can easily determine where to display the validation 
        ‘ result.
        Dim Result As Label = CType(p_asyncResult.AsyncState, Label)
    
        ‘ call the EndValidate method to get the result of
        ‘ the validation request.
        Try
            o_validator.EndValidateCard(p_asyncResult,x_result)
    
            If x_result.o_valid Then
                Result.Text = "Number Valid"
            Else 
                Result.Text = "Number Invalid"
            End If
         Catch x_ex As System.Web.Services.Protocols.SoapException
            ‘ display the content of the exception
            Select Case x_ex.Code.ToString()
                Case "Client.IllegalCharacter"
                    Result.Text = "Illegal Character"
                Case "Client.InvalidLength"
                    Result.Text = "Invalid Length"
                Case "Client.InvalidPrefix"
                    Result.Text = "Invalid Prefix"
                Case Else
                    Result.Text = "Unexpected Error"
            End Select
        End Try
    End Sub
  6. Finally, build this project by selecting Build Solution from the Build menu or pressing Ctrl+Shift+B.

Procedure 15-6. Test the Asynchronous Client

  1. Run the application by pressing Ctrl+F5 in Visual Studio .NET.

  2. Enter 4921 8352 2155 2042 for card number 1.

  3. Enter baddata for card number 2.

  4. Enter 12345 for card number 3.

  5. Click the Validate button. You will see the validation result messages appear one at a time as the asynchronous operation completes, invoking the callback method.

    Test the Asynchronous Client

Waiting for Asynchronous Method Completion

The IAsyncResult returned when you call the BeginValidateCard method has a property named AsyncWaitHandle, which contains a WaitHandle object. You can use this WaitHandle object to suspend execution of your program until the associated asynchronous operation completes. The WaitHandle class also contains static methods that allow you to synchronize the completion of more than one asynchronous operation. WaitHandle offers more flexibility than simply blocking until an asynchronous operation completes, allowing you to do the following:

  • Wait for a single asynchronous operation to complete.

  • Wait for the first of a set of asynchronous operations to complete.

  • Wait for all of a set of asynchronous operations to complete.

  • For all of the scenarios listed above, specify a time limit; if the timeout expires before the asynchronous operations complete, control returns to your program.

We detail the WaitHandle methods you use to achieve these results in the following table.

Method

Description

WaitOne

Waits for a single WaitHandle object.

WaitAll

A static method that waits for all WaitHandle objects contained in a WaitHandle[] to complete.

WaitAny

A static method that waits for any one of the WaitHandle objects ­contained in a WaitHandle[] to complete.

The following client example is very similar to the previous one. However, instead of using callbacks, we use WaitHandle.WaitAll to wait for all of the validation requests to return before displaying the results, meaning that the results are not displayed until the slowest response has been received.

To show that the client is still running and to demonstrate the timeout capabilities of the WaitHandle methods, we configure our WaitAll call to timeout every 100 milliseconds and update a ProgressBar control on the user interface.

Procedure 15-7. Modify the WindowsFormsClient User Interface

  1. Close the WindowsFormsClient project we created earlier, in the section "Using Callbacks to Signal Asynchronous Method Completion," if you still have it open in Visual Studio .NET.

  2. Using Windows Explorer, copy the WindowsFormsClient project folder from C:XMLWebServicesSBSProjectsChapter15CallBack to a new folder, named C:XMLWebServicesSBSProjects Chapter15WaitAll.

  3. Open the WindowsFormsClient project in the WaitAll folder in Visual Studio .NET.

  4. In Solution Explorer, double-click on the source file for Form1 (either Form1.cs or Form1.vb depending on which language you are using) to open the design view for Form1.

  5. With the Visual Studio .NET toolbox, add a ProgressBar control to Form1. The property values for the control are listed in the following table. The form should look like this:

    Modify the WindowsFormsClient User Interface

    Control

    Property

    Value

    ProgressBar

    Name

    ProgressBar

     

    Location

    300, 150

     

    Maximum

    5000

     

    Size

    150, 23

     

    Step

    100

Procedure 15-8. Modify the WindowsFormsClient Functionality

  1. In the Form1 design view, double-click on the Validate button to display the ValidateButton_Click method and then replace the existing method with the following version.

    Instead of passing a delegate instance to the BeginValidateCard method, we pass null (C#) or Nothing (Visual Basic .NET). Once we have submitted all the validation requests asynchronously to the XML Web service, we go into a loop based on the result of the WaitHandle.WaitAll method. If the method times out (after 100 milliseconds) and all of the WaitHandle objects have not been signaled, the method returns false, we update the ProgressBar control, and wait again. Once all of the asynchronous operations have completed, we call the ProcessResult method to process the response to the validation request.

    Example 15-21. C#

    private void ValidateButton_Click(object sender, 
        System.EventArgs e) {
        // create an IAsyncResult reference for each asynchronous
        // validation request that we can submit.
        IAsyncResult x_asyncResult1 = null;
        IAsyncResult x_asyncResult2 = null;
        IAsyncResult x_asyncResult3 = null;
    
        // create an ArrayList to hold the IAsyncResult.WaitHandle 
        // objects we use later to wait for asynchronous requests to
        // complete.
        ArrayList x_resultHandles = new ArrayList(3);
    
        // for the purpose of this example, it helps if we clear 
        // the Result labels and reset the ProgressBar before we 
        // submit the validation requests.
        Result1.Text = "";
        Result2.Text = "";
        Result3.Text = "";
        ProgressBar.Value = 0;
    
        // create and configure a ValidationObject for each card number
        // entered and asynchronously request validation from
        // the XML Web service via the proxy class. use the last
        // argument to pass a reference to the Result label in which
        // the result of the validation request should be displayed.
        if (CardNumber1.Text != "") {
            // create the ValidationObject that we will send to 
            // the XML Web service
            ValidationObject x_object = new ValidationObject();
    
            // assume the credit card is a VISA card and set the 
            // card type for the validation object.
            x_object.o_card_type = CARD_TYPE.VISA;
    
            // set the VISA card number
            x_object.o_card_number = CardNumber1.Text;
    
            // submit the request and add the returned 
            // IAsyncResult.AsyncWaitHandle to our ArrayList 
            // of WaitHandle objects
            x_asyncResult1 = 
                o_validator.BeginValidateCard(x_object, null, Result1);
            x_resultHandles.Add(x_asyncResult1.AsyncWaitHandle);
        }
    
        // do the same for CardNumber2 
        if (CardNumber2.Text != "") {
            ValidationObject x_object = new ValidationObject();
            x_object.o_card_type = CARD_TYPE.VISA;
            x_object.o_card_number = CardNumber2.Text;
            x_asyncResult2 = 
                o_validator.BeginValidateCard(x_object, null, Result2);
            x_resultHandles.Add(x_asyncResult2.AsyncWaitHandle);
        }
    
        // do the same for CardNumber3
        if (CardNumber3.Text != "") {
            ValidationObject x_object = new ValidationObject();
            x_object.o_card_type = CARD_TYPE.VISA;
            x_object.o_card_number = CardNumber3.Text;
            x_asyncResult3 = 
                o_validator.BeginValidateCard(x_object, null, Result3);
            x_resultHandles.Add(x_asyncResult3.AsyncWaitHandle);
        }
    
        // copy the ArrayList of IAsyncResult.WaitHandle objects
        // to an array
    
        WaitHandle[] x_handleArray = 
             (WaitHandle[])x_resultHandles.ToArray(typeof(WaitHandle));
    
        // wait for all handles to be signaled, but break every 100
        // milliseconds to update our progress meter
        while (!WaitHandle.WaitAll(x_handleArray, 100, false)) {
            ProgressBar.PerformStep();
        }
    
        // process each IAsyncResult that is not null
        if (x_asyncResult1 != null) ProcessResult(x_asyncResult1);
        if (x_asyncResult2 != null) ProcessResult(x_asyncResult2);
        if (x_asyncResult3 != null) ProcessResult(x_asyncResult3);
    }

    Example 15-22. Visual Basic .NET

    Private Sub ValidateButton_Click(ByVal sender As Object, _
        ByVal e As System.EventArgs) Handles ValidateButton.Click
    
        ‘ create an IAsyncResult reference for each asynchronous
        ‘ validation request that we can submit.
        Dim x_asyncResult1 As IAsyncResult =  Nothing 
        Dim x_asyncResult2 As IAsyncResult =  Nothing 
        Dim x_asyncResult3 As IAsyncResult =  Nothing 
    
        ‘ create an ArrayList to hold the IAsyncResult.WaitHandle 
        ‘ objects we use later to wait for asynchronous requests to
        ‘ complete.
        Dim x_resultHandles As ArrayList =  New ArrayList(3) 
    
        ‘ for the purpose of this example, it helps if we clear 
        ‘ the Result labels and reset the ProgressBar before we 
        ‘ submit the validation requests.
        Result1.Text = ""
        Result2.Text = ""
        Result3.Text = ""
        ProgressBar.Value = 0
    
        ‘ create and configure a ValidationObject for each card number
        ‘ entered and asynchronously request validation from
        ‘ the XML Web service via the proxy class. use the last
        ‘ argument to pass a reference to the Result label in which
        ‘ the result of the validation request should be displayed.
        If CardNumber1.Text <> "" Then
            ‘ create the ValidationObject that we will send to 
            ‘ the XML Web service
            Dim x_object As ValidationObject =  New ValidationObject() 
    
            ‘ assume the credit card is a VISA card and set the 
            ‘ card type for the validation object.
            x_object.o_card_type = CARD_TYPE.VISA
    
            ‘ set the VISA card number
            x_object.o_card_number = CardNumber1.Text
    
            ‘ submit the request and add the returned 
            ‘ IAsyncResult.AsyncWaitHandle to our ArrayList 
            ‘ of WaitHandle objects
            x_asyncResult1 = o_validator.BeginValidateCard(x_object, _
                Nothing, Result1)
            x_resultHandles.Add(x_asyncResult1.AsyncWaitHandle)
        End If
    
        ‘ do the same for CardNumber2 
        If CardNumber2.Text <> "" Then
            Dim x_object As ValidationObject =  New ValidationObject() 
            x_object.o_card_type = CARD_TYPE.VISA
            x_object.o_card_number = CardNumber2.Text
            x_asyncResult2 = o_validator.BeginValidateCard(x_object, _
                Nothing, Result2)
            x_resultHandles.Add(x_asyncResult2.AsyncWaitHandle)
        End If
    
        ‘ do the same for CardNumber3
        If CardNumber3.Text <> "" Then
            Dim x_object As ValidationObject =  New ValidationObject()
            x_object.o_card_type = CARD_TYPE.VISA
            x_object.o_card_number = CardNumber3.Text
            x_asyncResult3 = o_validator.BeginValidateCard(x_object, _
                Nothing, Result3)
            x_resultHandles.Add(x_asyncResult3.AsyncWaitHandle)
        End If
    
        ‘ copy the ArrayList of IAsyncResult.WaitHandle objects to 
        ‘ an array
    
        Dim x_handleArray() As WaitHandle = _
            CType(x_resultHandles.ToArray _
            (Type.GetType("System.Threading.WaitHandle")), _
            WaitHandle())
    
        ‘ wait for all handles to be signaled, but break every 100
        ‘ milliseconds to update our progress meter
        While Not WaitHandle.WaitAll(x_handleArray,100,False)
            ProgressBar.PerformStep()
        End While
    
        ‘ process each IAsyncResult that is not null
        If Not (x_asyncResult1 Is Nothing) Then
            ProcessResult(x_asyncResult1)
        End If
        If Not (x_asyncResult2 Is Nothing) Then
            ProcessResult(x_asyncResult2)
        End If
        If Not (x_asyncResult3 Is Nothing) Then
            ProcessResult(x_asyncResult3)
        End If
    End Sub
  2. Scroll to the top of the Form1 code view window, and then add the statements to import the Threading namespace. This step is required so that the code can access the WaitHandle class.

    Example 15-23. C#

    // import the Threading namespace
    using System.Threading;

    Example 15-24. Visual Basic .NET

    ‘ import the Threading namespace
    Imports System.Threading
  3. Modify the name of the ValidationCallback method, calling it ProcessResult. The functionality remains the same.

    Example 15-25. C#

    // Method for processing the result of 
    // asynchronous BeginValidateCard calls
    private void ProcessResult(IAsyncResult p_asyncResult) {

    Example 15-26. Visual Basic .NET

    Private Sub ProcessResult(ByVal p_asyncResult As IAsyncResult)
  4. If using C#, change the STAThread attribute on the Form1.Main method to MTAThread. If using Visual Basic .NET, you need to add a new Main method, specifying the MTAThread attribute. Your Main methods should look like the following. (Applications created with the Visual Studio .NET project templates automatically specify the STAThread attribute on the application’s Main method; you cannot use WaitHandle.WaitAll if the STAThread attribute is specified.)

    Example 15-27. C#

    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [MTAThread]
    static void Main() 
    {
        Application.Run(new Form1());
    }

    Example 15-28. Visual Basic .NET

    ‘ The main entry point for the application.    
    <MTAThread()> _
    Public Shared Sub Main()
        Application.Run(New Form1())
    End Sub
  5. Build the solution either by selecting Build Solution from the Build menu or pressing Ctrl+Shift+B.

Procedure 15-9. Test the WindowsFormsClient

  1. Run the application by pressing Ctrl+F5 in Visual Studio .NET.

  2. Enter 12345 for card number 1.

  3. Enter 4921 8352 2155 2042 for card number 2.

  4. Enter baddata for card number 3.

  5. Click the Validate button.

    You will see the progress bar change as the WaitHandle.WaitAll method times out repeatedly because of the delay we built into the ValidateCard method of our XML Web service. Then all validation results will appear at the same time, as you can see here.

    Test the WindowsFormsClient

Blocking on Asynchronous Method Completion

In the previous examples, we have called the EndValidateCard method to obtain the results of a completed asynchronous operation. However, if you call EndValidateCard and pass it the IAsyncResult of an asynchronous request that has not yet finished, your program will block until the asynchronous operation completes. The result is equivalent behavior to the synchronous calls we have used throughout other chapters of the book, although it does give you the flexibility to decide when your program will enter the blocked state.

This approach can be used if you have additional threads that you don’t mind being blocked while your main program thread continues to process normally. In addition, this approach might be appropriate if there are a finite number of things you can do before you must wait for the response from the XML Web service.

Polling to Determine Asynchronous Method Completion

The IsCompleted property of the IAsyncResult instance returned when you call the BeginValidateCard method is true if the asynchronous operation has completed and is false otherwise. By repeatedly checking the value of this property, you can determine when to process the method results.

This approach is valid only if you want to check on completion infrequently; a tight polling loop is inefficient and will affect program performance. If you need fast feedback on asynchronous operation completion, use the callback or WaitHandle approaches discussed earlier in this chapter.

Chapter 15 Quick Reference

To

Do This

Write an asynchronous XML Web service

Do nothing. The functionality for communicating asynchronously with XML Web services is implemented at the client end. All XML Web services support asynchronous communication automatically.

Make an asynchronous call to an XML Web service

Call the BeginWebMethodName method from the XML Web service client proxy class.

Get the results of an asynchronous call to an XML Web service

Call the EndWebMethodName method from the XML Web service client proxy class passing the IAsyncResult instance (returned when you called the BeginWebMethodName method) as an argument.

Use callbacks to signal when an asynchronous operation has completed.

Pass an AsyncCallback delegate instance to the BeginWebMethodName method.

Use .NET synchronization techniques to wait for an asynchronous operation to complete

Obtain the WaitHandle instance from the IAsyncResult.AsyncWaitHandle property of the asynchronous operations you want to wait for. Then use the WaitAll, WaitAny, or WaitOne methods of the WaitHandle class to wait for the operation to complete.

Poll an asynchronous operation to determine whether it has completed

Test the IAsyncResult.IsCompleted property of the asynchronous operation and see whether it is true.

Block program execution until an asynchronous operation completes

Call the EndWebMethodName method for the outstanding asynchronous operation.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.99.152