65.9K
CodeProject is changing. Read more.
Home

How to handle events during a web service call?

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.33/5 (17 votes)

Feb 4, 2004

3 min read

viewsIcon

67191

A C# class that allows to run web service asynchronous to handle events during them.

Introduction

Web Services are a cool thing. You can call them from any browser or other GUI, and since the Web Service Enhancement Kit is released, they don’t reduce you to a small amount of datatypes any more. They seem to fit for a real huge class of problems, but one critical question still isn’t that easy:

How to run them asynchronous to get more flexibility in the component that calls them?

Maybe you want to call a very slow web service and want to show a progress bar in your application showing the current state of action. Or you simply want to allow your application to run several routine jobs (triggered by timers or something) while the web service is running.

If you look into the proxy class of your webservices, you will find some methods that support asynchronous calls. For each web method, you find a corresponding Begin<webmethodname> and End<webmethodname> in it.

But how to use them easily without changing too many code in your application?

And how to prevent your application or the user running it from doing unpredictable things while the asynchronous webservice call?

Your application can only work correct if the return or out parameters of the web service have the final values, so what we need most of the time is a web service that runs synchronous to the application's workflow, but runs asynchronous regarding the amount of processor time it uses compared to the calling component. The calling component should not be blocked by the web service, so that it is able to manage its events while the web service is running.

The following C# class is a solution for this problem. It implements a “synchronous asynchronous webservice call”, that prevents your application from being blocked during web service calls.

Instead of calling the webservice directly, you have to call the static method CallWS of this class. You pass the in-parameters as a object[] and the out parameters as a ref object (which has to be an array inside, the problem is that .NET does not support ref object[], so we have to do it with a ref object that contains an array):

Let's look at a simple example with 3 in parameters and without out parameters:

int intReturnValue = CAsyncWebService.CallWS(myWSObject, 
                              "GetData",new object[]{a,b,c});

myWSObject is an instance of your web service and a, b, c are the in parameters of some type.

The return value remains the return value and your webservice attachments are also still there. You can leave all code the same, expect the single line of the Web service call that turns into a single line call of the asynchronous webservice call (or a multi line call block, if you have out parameters)!

The only comfort you lose by this is that the compiler cannot check if the parameter types are matching the web service parameter types, but I don’t have any solution for this problem yet (the premium solution would be a code generator, that creates a different CallWS-method for each web method, but this is much oversized for our small problem here).

Let's look to a more complex call, that uses 3 out parameters in addition to the example above:

object objOutParams = new object[]{d,e,f}; 
// d,e,f are out variables of some type


int intReturnValue = CAsyncWebService.CallWS(myWSObject, 
    "GetData",new object[]{a,b,c},ref objOutParams);
// d,e,f should contain other values now, but therefore

// we have to assign them back from the 

// objOutParams, because only their container was passed

// by ref, not the variables themselve 

// (.net does not support the combination "params ref object[] list") :


d =((object[])objOutParams)[0];
e =((object[])objOutParams)[1];
f =((object[])objOutParams)[2];

And now let's come to the code itself:

using System;
using System.Reflection;
using System.Collections;
using System.Diagnostics;
 
namespace MyCoolTools
{
  /// <summary>

  /// This class implements a synchron asynchron webservice !!!

  /// Sounds crazy, doesn't it ?

  /// The Problem we wanted to solve is,

  /// we want to run several things within the gui

  /// while a webservice is running (update progressbar, polling for messages etc.)

  /// The User and the part of the businesslogic that does the webservice call 

  /// should not recognize, that the call is asnychron,

  /// because the user would do some

  /// unpredictable things in this case and

  /// the businesslogic that calls the webservice

  /// expects filled results after the webservice call

  /// is finished. so if we call it asynchron

  /// we have to implement a lot of additional stuff

  /// in the businesslogic to not getting wrong behaviour.

  /// We only want the gui having the chance to run its

  /// event handlers (timer-events or something)

  /// while a webservice is running ! This class is the solution.

  /// Instead calling the webservice directly

  /// you have to call the static Method CallWS of this class.

  /// you pass the in-parameters as a object[]

  /// and the out parameters as a ref object (which has

  /// to be an array inside, the problem is that .net 

  /// does not support ref object[] so we have to do it with

  /// a ref object that contains an array) :

  /// 

  /// object objOutParams = new object[]{d,e,f}; 

      // d,e,f are out variables of some type


  /// int intReturnValue = CAsyncWebService.CallWS(myWSObject,

  ///           "GetData",new object[]{a,b,c},ref objOutParams);

  /// // a,b,c are in-parameters of some type

  /// // d,e,f should contain other values now, but therefore

  /// we have to assign them back from the 

  /// // objOutParams, because only their container was

  ///    passed by ref, not the variables themselve 

  /// // (.net does not support the combination "params ref object[] list") :

  /// 

  /// d =((object[])objOutParams)[0];

  /// e =((object[])objOutParams)[1];

  /// f =((object[])objOutParams)[2];

  /// 

  /// the return value remains the return value and your

  /// webservice attachments are also still there. 

  /// you can leave all code the same, expect the single line

  /// of the Web service call that turns into

  /// a single line call of the async webservice call

  /// (or a multi line call block, if you have out parameters) !

  /// Uwe Arndt, (www.uwearndt.de) 03.02.04

  /// </summary>

  public class CAsyncWebService
  {
    private static bool m_blnWSRunning=false;
    private static object m_objWSResult=null;
    private static object m_objWSProxy=null;
    private static string m_strWebMethod=string.Empty;
    private static object m_WSOutParams=null;

    public static object CallWS(object wsProxy,string strWebMethod)
    {
      return CallWS(wsProxy,strWebMethod,new object[]{});
    }

    public static object CallWS(object wsProxy,
          string strWebMethod,object[] objWSParams)
    {
      object emptyOutArray = new object[]{};
      return CallWS(wsProxy,strWebMethod,objWSParams,ref emptyOutArray);
    }

    public static object CallWS(object wsProxy, 
          string strWebMethod,object[] objWSParams, 
          ref object objWSOutParams)
    {
      // set members from parameters, that the callback

      // method needs (we cant pass them to it

      // directly because the callback-method-interface is fixed)

      m_objWSProxy=wsProxy;
      m_strWebMethod=strWebMethod;
      m_WSOutParams=objWSOutParams;

      // read the url to call

      System.Net.WebRequest wrq = 
       System.Net.WebRequest.Create(InvokeGetPropertyOfPrivateObject(m_objWSProxy,
       "Url").ToString());
      System.AsyncCallback objAsyncCallBack = 
         new System.AsyncCallback(MyAsyncCallBack);

      // we have to append two parameters to the parameter

      // list, therefore we need a arraylist, 

      // because the normal array doesnt support "Add"

      // (i know the IList-Interface, but it doesnt

      // work here, because our other problem with the array is, that it is fixed)

      ArrayList al = new ArrayList();
      for(int i = 0;i<objWSParams.Length;i++)
      {
        al.Add(objWSParams[i]);
      }
      al.Add(objAsyncCallBack);
      al.Add(wrq);

      m_blnWSRunning=true;
      object objInParams=al.ToArray();
      InvokeMethodOfObject(m_objWSProxy,
         "Begin"+m_strWebMethod,ref objInParams);

      // wait until MyAsyncCallBack is called setting the m_blnWSRunning to false

      // the gui can do its events while this time

      while(m_blnWSRunning)
      {
        System.Windows.Forms.Application.DoEvents();
      }

      objWSOutParams=m_WSOutParams;
      return m_objWSResult;
    }

    private static void MyAsyncCallBack(IAsyncResult ar)
    {
      //add ar to parameter array

      ArrayList al = new ArrayList();
      al.Add(ar);
      for(int i = 0;i<((object[])m_WSOutParams).Length;i++)
      {
        al.Add(((object[])m_WSOutParams)[i]);
      }

      m_WSOutParams = al.ToArray();
      m_objWSResult = InvokeMethodOfObject(m_objWSProxy, 
                     "End"+m_strWebMethod,ref m_WSOutParams);

      // delete ar from returned parameter array

      al.Clear();
      for(int i = 1;i<((object[])m_WSOutParams).Length;i++)
      {
        al.Add(((object[])m_WSOutParams)[i]);
      }
      m_WSOutParams= al.ToArray();

      m_blnWSRunning=false;
    }

    // invokes a property of a private object,

    // that means an object, which you can see

    // in the debugger, but to whom you can not cast to,

    // because its type isnt visible at compiletime

    // returns the return value of the property

    private static object 
      InvokeGetPropertyOfPrivateObject(object objPrivate,
      string strProperty)
    {
      object o = null;
      try
      {
        System.Type st = ((object)objPrivate).GetType();
        PropertyInfo pi = st.GetProperty(strProperty);
        MethodInfo mi = pi.GetGetMethod();
        o = mi.Invoke(objPrivate,new object[0]);
      }
      catch(Exception ex)
      {
        Debug.WriteLine(ex.Message);
      }
      return o;
    }

    // invokes a method of the given object and returns its return value

    private static object InvokeMethodOfObject(object obj, 
                 string strMethod,ref object arMethodParams)
    {
      object o = null;
      try
      {
        object[] objParams = (object[])arMethodParams;
        System.Type st = ((object)obj).GetType();
        o = st.InvokeMember(strMethod,BindingFlags.DeclaredOnly | 
              BindingFlags.Public | BindingFlags.NonPublic | 
              BindingFlags.Instance | BindingFlags.InvokeMethod, 
              null,obj,objParams);
      }
      catch(Exception ex)
      {
        Debug.WriteLine(ex.Message);
      }
      return o;
    }
  }
}