Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Building a Better Wait Page

0.00/5 (No votes)
17 Mar 2005 2  
Building a better wait page using asynchronous programming and the XmlHttpRequest object.

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 XmlHttpRequests, 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);    //simulate a long process by waiting for ten seconds

    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;

// this tells the wait page to check the status every so often

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) 
    {
        // only if "OK"

        if (req.status == 200) 
        {
            if(req.responseText == "1")
            {
                // a "1" means it is done, so here is where you redirect

                // to the confirmation page

                document.location.replace(nextPageUrl);
            }
            // NOTE: any status other than 200 or any response other than

            // "1" require no action

        }
    }
}

/*
Note that this tries several methods of creating the XmlHttpRequest object,
depending on the browser in use. Also note that as of this writing, the
Opera browser does not support the XmlHttpRequest.
*/
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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here