Introduction
Any one who has ever written a web application that needed to perform some long running task has probably implemented a 'wait' page. Since web users are notoriously impatient, the wait page gives the user some visual feedback that something is indeed actually happening and assures them that if they just sit back, relax, and wait patiently, the next page really is on the way.
The Problem
To perform your long running process and display the friendly wait page to the user requires two things to be done at once. Unfortunately, most web programming techniques are not well suited to multi-tasking in this way. Add to the equation the fact that the Internet is a client-server architecture, where the server only responds when the client makes a request. That means that the server cannot 'tell' the browser, when it is done with a long process after the client has already received the wait page. In classic ASP and other web programming languages, there were few options to address either issue.
One frequently-implemented option was to redirect from your input page to a wait page, and then almost immediately redirect to another page that would actually do the processing. This approach took advantage of a browser 'feature' that didn't allow the browser to render the next page until it was completely finished processing. In the meantime, the previous page stayed visible. This approach had two main drawbacks: if your wait page contained any animated GIFs (which wait pages are famous for), they would stop animating as soon as the long running page was requested; and secondly: the browser gave the impression that it was 'doing something' (throbber was spinning), but nothing was happening. Impatient users quickly learned to hit 'Stop' or 'Back' and try the process again, causing even more problems and obviously not very desirable.
Another solution was to have the wait page refresh continuously every few seconds, checking to see if the long running process had completed. This required the process to set a flag in the database or some other signal, and the user experience of watching the wait page reload over and over was less than desirable.
The Solution
With the advent of ASP.NET, web programmers could now take advantage of an asynchronous programming model. So now the problem of 'doing two things at once' was solved. But what about the client-server architecture that prevented the server 'calling back' to the client? Well, a handy-dandy little tool called XmlHttpRequest
is the answer. (You might have seen this little gem recently starring in Google Suggest and making some big waves.) By combining an asynchronous method call and background XmlHttpRequest
s, your wait page suddenly gets a lot smarter.
Using the code
There are four main components that make up this solution, each outlined below:
- Input page - responsible for firing off the asynchronous event
- Wait page - responsible for checking the status of the process and entertaining the user
- CheckStatus page - simply indicates if the process has completed yet or not
- Confirmation page - displayed after the process has completed
Input page
This is the page that the user uses to kick off the long-running event. In the page's code-behind, the following code fires off the process using .NET's asynch programming model. Also note that the resulting IAsyncResult
is saved into the Session
so we can query it later on.
string confirmationNumber;
private void Button_Click()
{
IAsyncResult ar = DoSomethingAsync("abc");
Session["result"] = ar;
Response.Redirect("wait.aspx");
}
private IAsyncResult DoSomethingAsync(string someParameter)
{
DoSomethingDelegate doSomethingDelegate =
new DoSomethingDelegate(DoSomething);
IAsyncResult ar = doSomethingDelegate.BeginInvoke(someParameter,
ref confirmationNumber, new AsyncCallback(MyCallback), null);
return ar;
}
private delegate bool DoSomethingDelegate(string someParameter,
ref string confirmationNumber);
private void MyCallback(IAsyncResult ar)
{
AsyncResult aResult = (AsyncResult) ar;
DoSomethingDelegate doSomethingDelegate =
(DoSomethingDelegate) aResult.AsyncDelegate;
doSomethingDelegate.EndInvoke(ref confirmationNumber, ar);
}
private void DoSomething(string someParameter, ref string confirmationNumber)
{
Thread.Sleep(10000);
confirmationNumber = "DONE!";
Session["confirmationNumber"] = confirmationNumber;
}
Wait Page
The wait page doesn't need any code-behind code because it is simply a display page that uses JavaScript and XmlHttpRequest
to poll the server to see when the long-running process is done. Essentially, the page makes a client-side request of the CheckStatus.aspx page and then acts according to the response. All of this happens without refreshing the page. It is also important to note that this technique is cross-browser compatible, working in IE5+, Mozilla, Netscape 6+, Firefox 1+, and others. Note that as of this writing, the Opera browser does not support the XmlHttpRequest
object, but adventurous programmers could emulate this functionality using a hidden IFrame as Google Suggest does. That is left as an exercise for the programmer =).
<script language="javascript">
<!--
var pollInterval = 1000;
var nextPageUrl = "confirmation.aspx";
var checkStatusUrl = "checkStatus.aspx";
var req;
window.setInterval("checkStatus()", pollInterval);
function checkStatus()
{
createRequester();
if(req != null)
{
req.onreadystatechange = process;
req.open("GET", checkStatusUrl, true);
req.send(null);
}
}
function process()
{
if(req.readyState == 4)
{
if (req.status == 200)
{
if(req.responseText == "1")
{
document.location.replace(nextPageUrl);
}
}
}
}
function createRequester()
{
try
{
req = new ActiveXObject("Msxml2.XMLHTTP");
}
catch(e)
{
try
{
req = new ActiveXObject("Microsoft.XMLHTTP");
}
catch(oc)
{
req = null;
}
}
if(!req && typeof XMLHttpRequest != "undefined")
{
req = new XMLHttpRequest();
}
return req;
}
</script>
Also note the use of document.location.replace();
when doing the redirect to the confirmation page. This effectively removes the wait page from the browser's history so that the user does not hit the 'Back' button and start the process over inadvertently.
CheckStatus page
This is really a very simple page. It just checks the IAsyncResult
to see if it is completed, and returns a bit (0 or 1) indicating that status.
private void Page_Load(object sender, System.EventArgs e)
{
AsyncResult ar = (AsyncResult) Session["result"];
if(ar.IsCompleted)
Response.Write("1");
else
Response.Write("0");
Response.End();
}
Confirmation Page
Once the long-running process has completed, the wait page will get a signal from the CheckStatus page that it is OK to proceed. Once that happens, the wait page will redirect to the confirmation page and display the results to the user - pretty regular stuff that you can implement as needed.
Points of Interest
With all of the interest in Google Suggest and the XmlHttpRequest
object lately, I thought it would be fun to show how it can solve a real-world problem. Plus, I thought it was about time that the lowly wait page was given a little polish of its own.
History
- 03.17.2005 - Initial version released.