Saturday, November 04, 2006

(ZT)Asynchronous Web Services in ASP.NET 2.0

http://www.dotnetbips.com/articles/displayarticle.aspx?id=519

 

Introduction

By default when you call a web method from the client application it is a synchronous call. that means unless the web method returns the further code will not be executed. However, this approach may not be suitable in each and every scenario and you may need an asynchronous way to execute the web methods. This article is going to explain how to do just that.

A sample scenario

Imagine that you are developing a portal using ASP.NET 2.0. As a part of the feature set you want to provide a facility whereby users can get a comparative price list of computer books. The users will specify a book title whose price list is to be generated. For getting the cost of the book you consume web services exposed by various book suppliers. That means for a book title you call many web methods that return the cost of the title by individual suppliers. You then collectively display the costs to the user. The scenario is explained pictorially below:

 

As you can see the Web Service 1 and 2 are provided by the book suppliers and reside on their own web servers. Your portal is hosted on its own web server. The end user is accessing your portal. Your portal in turn calling Web Service 1 and Web Service 2. Let's say that Web Service 1 is taking 5 seconds to execute and Web Service 2 is taking 10 seconds to execute. If you call both of the web services in synchronous manner then you need to spend 15 seconds. Won't it be nice if we call Web Service 1 and Web Service 2 in parallel? This way the resultant time will be 10 seconds only.

Asynchronous Web Methods 1.x approach and 2.0 approach

Calling web methods asynchronously is not a new thing in ASP.NET. It existed in ASP.NET 1.0 and 1.1 also. What is new in ASP.NET 2.0 is the programming approach. In ASP.NET 1.x Microsoft used BeginXXXX and EndXXXX pattern where XXXX is the name of the web method. This approach is in fact the standard approach for asynchronous operations in the .NET framework. No doubt this approach worked as expected but it was bit tedious to work. For example, you need to create your own asynchronous callback functions and then call EndXXXX to retrieve the return value of the web method. Though this approach is still possible in ASP.NET 2.0 there is a simplified way too.

The proxy of your web service automatically creates methods of the form XXXXAsync where XXXX is the name of your web method. For example, if your web method name is HelloWorld then there will be a method called HelloWorldAsync. Calling this method invokes the web method in asynchronous fashion. After the web method is complete it raises an event of the form XXXXCompleted where XXXX is the name of your web method. Taking the above example further you will have HelloWorldCompleted event for your proxy. You can use this event to trap the return value of the web method.

Now that you know the asynchronous execution model of web services let's see how it looks like at code level.

Creating the Web Services

 

Create a new web site in Visual Studio and add two web services (WebService1.asmx and WebService2.asmx) to it. These two web services represent the web services of two independent book suppliers. Add two SQL Server databases (Database1.mdf and Database2.mdf) in the App_Data folder of the web site. These two database represent the databases of two independent book suppliers. Create a table called Books in each database. The structure of the Books table is shown below:

 

Add a web method called GetCost() in both of the web services. The GetCost() web method is shown below:

[WebMethod]
public decimal GetCost(string title)
{
SqlConnection cnn = new SqlConnection
(@"Data Source=.\SQLEXPRESS;AttachDbFilename=
|DataDirectory|Database1.mdf;
Integrated Security=True;User Instance=True");
cnn.Open();
SqlCommand cmd = new SqlCommand();
cmd.Connection = cnn;
cmd.CommandText = "select cost,discount
from books where title=@title";
SqlParameter p = new SqlParameter("@title", title);
cmd.Parameters.Add(p);
SqlDataReader reader = cmd.ExecuteReader();
decimal bookCost = -1;
while (reader.Read())
{
decimal cost = reader.GetDecimal(0);
int discount = reader.GetInt32(1);
bookCost = cost - ((cost * discount) / 100);
}
reader.Close();
cnn.Close();
return bookCost;
}

The only difference in GetConst() web method from WebService1 and WebService2 is the database connection string. Notice the database connection string syntax that points to the databases from the App_Data folder. The GetCost() web method accepts the title of the book and returns its cost after deducting discount. The data access code is a typical ADO.NET code involving SqlConnection, SqlCommand and SqlDataReader.

This completes your web services. Now we will consume these web services.

Creating Proxy for Web Services

Before you consume any web service you must create proxy for it. You can create proxy by adding a "Web Reference" to the web service. Right click on your web site and choose "Add Web Reference" menu option. This will open a dialog as shown below:


In the URL text entry region you can either specify the complete URL of the web service file (.asmx) or you can click on "Web Services in this solution" option. Since both of our web service are in the same web site the later option will be handy. Clicking on the "Web Services in this solution" link changes the dialog as shown below:


Click on each web service one by one and click on "Add Reference" button. Make sure to specify Web reference name as BookSupplier1 and BookSupplier2 respectively.


Now you are ready with the proxies and can call the web methods.

Calling web methods asynchronously

Open the default web form in the Visual Studio IDE and design it as shown below:


The web form consists of a textbox, button and a bulleted list control. The textbox is used to specify the book title whose price is to be compared. The button calls the web methods in asynchronous fashion and the return results are displayed in the bulleted list. Also, set the Async property of the web form to true. It is required that whenever you invoke any  asynchronous operation in a web form you set the Async property of the web form to true. The Async property is set in the @Page directive of the web form.

<%@ Page Language="C#" AutoEventWireup="true" 
CodeFile="Default.aspx.cs" Inherits="_Default"
Async="true" %>

The Click event handler of "Get Price Comparison" button is as follows:

protected void Button1_Click
(object sender, EventArgs e)
{
BookSupplier1.WebService1 supplier1 =
new BookSupplier1.WebService1();
BookSupplier2.WebService2 supplier2 =
new BookSupplier2.WebService2();

supplier1.GetCostCompleted += new
BookSupplier1.GetCostCompletedEventHandler
(supplier1_GetCostCompleted);
supplier2.GetCostCompleted += new
BookSupplier2.GetCostCompletedEventHandler
(supplier2_GetCostCompleted);

supplier1.GetCostAsync(TextBox1.Text, BulletedList1);
supplier2.GetCostAsync(TextBox1.Text, BulletedList1);

}

The code creates a proxy objects each for WebService1 and WebService2. It then wires GetCostCompleted event of both of the proxy objects. Notice how this event got generated in the proxy automatically. The GetCostCompleted event is based on GetCostCompletedEventHandler delegate. The GetCostCompletedEventHandler delegate is also generated automatically for us. The signature of these event handlers will be clear shortly. Then the code invokes GetCost() method asynchronously. Notice how GetCostAsync() method is generated automatically for you. The first parameter of GetCostAsync() method is the title of the book and the second parameter is an arbitrary user state. In our example we pass the reference of the bulleted list control as the second parameter.

The two event handlers of the GetCostCompleted event are shown below:

void supplier1_GetCostCompleted
(object sender, BookSupplier1.GetCostCompletedEventArgs e)
{
if (e.Error != null)
{
throw e.Error;
}
BulletedList list = (BulletedList)e.UserState;
list.Items.Add("Quote from BookSupplier1 : "
+ e.Result.ToString("C"));
}

void supplier2_GetCostCompleted
(object sender, BookSupplier2.GetCostCompletedEventArgs e)
{
if (e.Error != null)
{
throw e.Error;
}
BulletedList list = (BulletedList)e.UserState;
list.Items.Add("Quote from BookSupplier1 : "
+ e.Result.ToString("C"));
}

The first parameter of the event handler is as usual object. The event argument parameter is of type GetCostCompletedEventArgs. This event argument class is also created automatically for you by the proxy. The GetCostCompletedEventArgs class provides some important properties. The Error property of GetCostCompletedEventArgs class returns an Exception that might have happened during the asynchronous call execution. Our code checks if any exception was at all thrown and if yes then the same exception is thrown again. The UserState property of GetCostCompletedEventArgs class gives a reference to the same state object that we passed in the second parameter of GetCostAsync() method. In our example we passed the bulleted list. The Result property of GetCostCompletedEventArgs class gives the return value of the web method under consideration. In our example we simply add this value in the bulleted list control.

That's it! You just invoked web methods asynchronously using the new event driven model of ASP.NET 2.0. Easy and neat. Isn't it? Now run the web form, enter some book title existing in the database and click on "Get Price Comparison" button. You should see something as shown below:

0 Comments:

Post a Comment

<< Home