Hands up! Who thinks that the current programming model for asynchronously calling Web services (and not only that) is just a bit too clumsy? The ASP.NET Web services proxy generation framework currently creates proxies that offer asynchronous invocation via the async pattern used throughout the .Net Framework. While this pattern is powerful compared to older days, it is difficult for a lot of people to understand. And it is always quite a bunch of work and lines of code to implement. Honestly, some of us do not work directly with callbacks or delegates if we can have some higher level event structures. And as a logical consequence, it would be extremely helpful if we could invoke asynchronous Web service calls by simply making calls and then handling events to indicate completion. True love.
The so called RAD Async feature in ASMX v2 allows developers to use an event-based model for invoking Web services asynchronously which should result in much cleaner and much more stable code at the end of the day. This obviously needs some changes in the generated Web service proxy. For each Web service operation, there will be up to four things generated in the resulting service proxy implementation.
First, there is a class [OperationName]CompletedEventArgs which inherits from System.ComponentModel.AsyncCompletedEventArgs. This new class will provide the async result via a property called Result. The type of Result is the type of the synchronous method’s return value. So if you have a method in the Web service called GetData that returns a DataSet, you will have a corresponding proxy class called GetDataCompletedEventArgs with a property Result of type System.Data.DataSet. This generated class will also contain a property for each output parameter, if any.
The second thing that will be generated is a delegate named [OperationName]CompletedEventHandler which inherits from System.ComponentModel.AsyncCompletedEventHandler. This delegate has two parameters, the sender and an instance of [OperationName]CompletedEventArgs. Next thing that will be generated is an event on the proxy class itself. The name of this event is [OperationName]Completed and its type will be the delegate [OperationName]CompletedEventHandler.
Finally, the asynchronous method itself is generated. There will actually be two overloads of this method. Both overloads will access all in and ref parameters. One of the overloads will also accept a parameter named asyncState of type object. This parameter will be used as a cookie to enable invoking an asynchronous method multiple times concurrently by the same client then distinguishing these calls when the [OperationName]Completed event is fired.
The best thing to do now is take a look at an example. Please consider the following WebMethod signature:
[WebMethod] public DataTable GetProducts() { // … }
When we point wsdl.exe to the WSDL of the appropriate Web service we get code generated similar to the one below. Please note that all the attributes and comments have been omitted for brevity. I also just show the signature of the methods, no internal implementation.
public class DataService : System.Web.Services.Protocols.SoapHttpClientProtocol { private System.Threading.WaitCallback GetProductsOperationCompleted; public event GetProductsCompletedEventHandler GetProductsCompleted;
public System.Data.DataTable GetProducts() {…} public System.IAsyncResult BeginGetProducts( System.AsyncCallback callback, object asyncState) {…} public System.Data.DataTable EndGetProducts( System.IAsyncResult asyncResult) {…} public void GetProductsAsync() {…} public void GetProductsAsync(object userToken) {…} private void OnGetProductsOperationCompleted(object arg) {…} public new void CancelAsync(object userToken) {…} } public delegate void GetProductsCompletedEventHandler(object sender, GetProductsCompletedEventArgs args);
public class GetProductsCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs { private object[] results; internal GetProductsCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userToken) : base(exception, cancelled, userToken) {…} public System.Data.DataTable Result {get {…}} }
With this proxy file in place we can use the generated proxy code in a Windows Forms application like this (just a snapshot, not everything is shown):
private DataService ws;
public Form1() { InitializeComponent(); ws = new DataService(); ws.GetProductsCompleted += GetProductsCompletedEventHandler(ws_GetProductsCompleted); }
Now, this is way easier than before. And if you still want to use the more hardcore and ‘real developer’-like approach and do it all on your own: go ahead, this is still supported. BTW, the inner workings of the RAD Async feature is based on a class called BackgroundWorker. The pattern that the BackgroundWorker class defines is used throughout the .NET Framework 2.0. Not only the new Web service proxy classes use it but also the new asynchronous ADO.NET functionality.