Asynchronous Web Services in ASP.NET 2.0
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 BookSupplier2 : "
+ 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: