Introduction
Processing long running tasks (i.e., SQL query execution, Web Services calls, etc.) has always been an important feature for high demand web applications. One problem these applications face is that of keeping the client UI responsive as the task executes.
Many sites, like Expedia, Travelocity etc. use intermediate 'Please Wait...' pages to work around the problem. Other sites, like Google Maps use client-side features such as XMLHTTP to make such requests without moving the user from the current page. The purpose of this article is to show how to use XMLHTTP along with ASP.NET to make the end user experience a bit more tolerable.
Background
Architecturally speaking, a key feature of ASP.NET is its objects have a short lifespan. This is extremely important to high demand web sites because it allows the application to execute quickly and efficiently and still be able to maintain a high level of concurrent users. The key factor behind keeping an ASP.NET web site responsive is to be aware of the framework constructs that control page execution.
A lot of developers know that to create a simple web page in ASP.NET one must use the System.Web.UI.Page
class. However, some of them might not be aware that there are other ways to handle requests made to the application. This is where System.Web.IHttpHandler
and System.Web.IHttpAsyncHandler
interfaces come into play.
IHttpHandler and IHttpAsyncHandler: A Quick Look
All request execution in ASP.NET happens through the System.Web.IHttpHandler
interface. If you look at the object diagram of the System.Web.UI.Page
class, you can see that it inherits from the System.Web.UI.TemplateControl
and implements the System.Web.IHttpHandler
interface. The class definition and object hierarchy looks like this:
public class Page : TemplateControl, IHttpHandler
The IHttpAsyncHandler
interface inherits from IHttpHandler
and defines two extra methods, BeginProcessRequest
and EndProcessRequest
respectively. These methods make up the mechanism needed to handle asynchronous calls within ASP.NET.
For this type of scenario, it is better to use the IHttpAsyncHandler
rather than a Page
object because of all the extra work (child control rendering, event handler notification, etc.) the Page
performs per request. For more information on IHttpAsyncHandler
, please see the MSDN documentation.
A Poor Man's Guide to Threading in ASP.NET
There are three simple ways you can accomplish threading within your ASP.NET application:
- Using the
System.Threading.ThreadPool
.
- Using a custom delegate and calling its
BeginInvoke
method.
- Using custom threads with the aid of
System.Threading.Thread
class.
The first two methods offer a quick way to fire off worker threads for your application. But unfortunately, they hurt the overall performance of your application since they consume threads from the same pool used by ASP.NET to handle HTTP requests. In other words, if you invoke five pool/delegate threads within a request, your application will only have 20 available threads remaining in its pool (by default, ASP.NET gives every application 25 worker threads). The way to get around this problem is to use the third option since the threads created are not part of your application's pool.
If you would like to read more on threading in ASP.NET, please see the References section.
Stepping Through the Code
The example used with this article is that of a simple weather service. The UI allows the user to enter US Zip codes to check current weather condition of cities. The reason why I chose a web service call is because it gives a great 'real world' example on how to use the asynchronous capabilities of ASP.NET and XMLHTTP.
When a user selects an entered zipcode from the list and hits the Check Weather button, two async requests are created, one to the weather checker handler and the other to the web service. A DHTML progress bar is used to keep the user 'entertained' while this process executes. Once a response is received by the client, the progress bar is stopped and the UI updated.
The main class behind this example is the AsyncTaskHandler
class. This class is mapped to an .ashx file so ASP.NET can hook up the request chain. This class implements two interfaces the IHttpAsyncHandler
, mentioned earlier, and the System.Web.SessionState.IRequiresSessionsState
interface that is used to tell ASP.NET that this handler has access to session state. The class then uses two classes AsyncRequestResult
and AsyncRequest
to process the request.
AsyncRequestResult
implements the System.IAsyncResult
interface so ASP.NET can call the appropriate methods necessary to perform a call back. The AsyncRequest
class is used to house a call to an external weather Web Service and write back the response to the waiting client. The collaboration between the objects can be seen by the following diagram:
- As soon as the handler receives a request, it creates an object of type
AsyncRequestResult
.
- Using this object as a constructor parameter, the handler then creates an object of type
AsyncRequest
.
- Finally, the handler creates a worker thread of type
System.Thread.Thread
and uses the Process
method of AsyncRequest
and returns the created AsyncRequestResult
back to ASP.NET and a response to the client.
Most of the core logic takes place within the AsyncRequest.Process
method. Within this method, the associated AsyncRequestResult
object is used to make a call to the weather information web service and to return an HTML snippet back to the client. The method looks as follows:
public void Process()
{
try
{
string strZip = Result.AsyncState as string;
int zipCode = Int32.Parse(strZip);
string message = Result.GetWeather(zipCode);
Result.Context.Response.Write(message);
}
finally
{
Result.Complete();
}
}
The actual call to the weather web service under the AsyncRequestResult.GetWeather
method looks like:
public string GetWeather(int zipCode)
{
string message = "";
try
{
ExtendedWeatherInfo info =
weatherService.GetExtendedWeatherInfo(zipCode);
message = string.Format("<h2>{0} - {1}" +
"</h2>Current Temperature: {2}a<br>Feels Like: {3}.",
zipCode.ToString(), info.Info.Location,
info.Info.Temprature, info.Info.FeelsLike);
}
catch
{
message = "An error was encountered while calling web service.";
}
return message;
}
Now that you have a quick overview of how the code works, let's see how the client creates the request and handles the response.
Using XMLHTTP Within IE
To make an HTTP request programmatically through JavaScript, you need to use XMLHTTP. For quite some time, Microsoft had bundled a version of this technology within their MSXML parser. To create a JavaScript object that binds to this feature, you do the following:
var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
This function will make a full asynchronous request to a URL and attach a callback function:
function postRequest(url)
{
var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
xmlhttp.Open("POST", url, true);
xmlhttp.onreadystatechange =
function ()
{
if (xmlhttp.readyState == 4)
{
var response = xmlhttp.responseText;
divResponse.innerHTML += "<p>" + response + "</p>";
stopProgressBar();
}
}
xmlhttp.Send();
}
From here, while we wait for the async handler to execute the long processing request, we show the users a simple little progress bar showing them that their request is being processed.
The following diagram shows how the execution of the request from XMLHTTP takes place:
- An
XMLHTTP
object is created when the user clicks on the Check Weather button.
- This object creates an async (non-blocking) request to weatherchecker.ashx.
- A temp callback is attached to the object to handle the response from the handler.
- Another JavaScript function is called to start the progress bar and display the 'Processing request...' text.
- Whenever the handler is finished executing the call to the web service, a response is generated back to the client's temp callback.
- The temp callback is executed in the client, the response text is set to a
<div>
, and the progress bar is stopped.
The final result from this process looks like:
Conclusion
Having the ability of executing long running tasks asynchronously is a huge benefit for any web application regardless of size. I hope that this article shows developers an alternate way of tackling this design problem. Also, I hope that developers are more conscious when making decisions to add threading to their ASP.NET applications.
Please feel free to make any comments or suggestions to this article by using the forums below.
Reference
During my writing of this article, I used the following resources: