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};
int intReturnValue = CAsyncWebService.CallWS(myWSObject,
"GetData",new object[]{a,b,c},ref objOutParams);
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
{
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)
{
m_objWSProxy=wsProxy;
m_strWebMethod=strWebMethod;
m_WSOutParams=objWSOutParams;
System.Net.WebRequest wrq =
System.Net.WebRequest.Create(InvokeGetPropertyOfPrivateObject(m_objWSProxy,
"Url").ToString());
System.AsyncCallback objAsyncCallBack =
new System.AsyncCallback(MyAsyncCallBack);
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);
while(m_blnWSRunning)
{
System.Windows.Forms.Application.DoEvents();
}
objWSOutParams=m_WSOutParams;
return m_objWSResult;
}
private static void MyAsyncCallBack(IAsyncResult ar)
{
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);
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;
}
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;
}
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;
}
}
}
I work as a freelancer software developer for several companies, mainly in microsoft environments.
At the moment i am working on a network management system in .net with SAP-Connection for the Deutsche Börse Systems AG in Frankfurt/Main.
My main interests besides .net and the outcoming Yukon-SQL-Server are chess and table tennis.
You can find more information on my Web-Site :
www.uwearndt.de
If you have any suggestions or remarks to my articles, please write to mail@uwearndt.de