65.9K
CodeProject 正在变化。 阅读更多。
Home

构建一个更好的等待页面

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.43/5 (37投票s)

2005年3月17日

4分钟阅读

viewsIcon

330450

downloadIcon

2681

使用异步编程和 XmlHttpRequest 对象构建更优秀的等待页面。

引言

任何编写过需要执行一些长时间运行任务的 Web 应用程序的人,可能都实现过“等待”页面。由于 Web 用户出了名地没有耐心,等待页面会给用户一些视觉反馈,表明确实有事情在发生,并向他们保证,只要他们坐好、放松、耐心等待,下一个页面就会到来。

问题

要执行长时间运行的进程向用户显示友好的等待页面,需要同时完成两件事。不幸的是,大多数 Web 编程技术都不适合以这种方式进行多任务处理。再加上 Internet 是一个客户端-服务器架构,服务器仅在客户端发出请求时响应。这意味着服务器在客户端收到等待页面后,在长时间进程完成后无法“告诉”浏览器。在经典的 ASP 和其他 Web 编程语言中,解决这两个问题几乎没有选择。

一个经常实现的选项是从输入页面重定向到一个等待页面,然后几乎立即重定向到一个实际执行处理的页面。这种方法利用了浏览器的一个“特性”,该特性不允许浏览器在完成处理之前渲染下一个页面。在此期间,上一个页面保持可见。这种方法有两个主要缺点:如果您的等待页面包含任何动画 GIF(等待页面常有),一旦请求长时间运行的页面,它们就会停止动画;其次:浏览器给人的印象是它在“做什么”(旋转的加载指示器),但实际上什么都没发生。没有耐心的用户很快就会学会按下“停止”或“后退”按钮,然后再次尝试该过程,从而导致更多问题,显然不是非常理想。

另一种解决方案是让等待页面每隔几秒钟不断刷新,检查长时间运行的进程是否已完成。这需要进程在数据库中设置一个标志或其他信号,而用户看着等待页面一遍又一遍地重载的用户体验并不理想。

解决方案

随着 ASP.NET 的出现,Web 程序员现在可以利用异步编程模型。这样,“同时做两件事”的问题就解决了。但客户端-服务器架构阻止服务器“回调”客户端的问题呢?嗯,一个非常方便的小工具,名为 XmlHttpRequest,就是答案。(您可能最近在 Google Suggest 中看到过这个小宝石,它掀起了一些波澜。)通过结合异步方法调用和后台 XmlHttpRequest,您的等待页面会变得更加智能。

使用代码

这个解决方案主要由四个组件组成,每个组件都概述如下:

  • 输入页面 - 负责触发异步事件
  • 等待页面 - 负责检查进程状态并娱乐用户
  • CheckStatus 页面 - 仅指示进程是否已完成
  • 确认页面 - 在进程完成后显示

输入页面

这是用户用来启动长时间运行事件的页面。在页面的代码隐藏中,以下代码使用 .NET 的异步编程模型启动进程。另请注意,生成的 IAsyncResult 被保存在 Session 中,以便我们稍后查询它。

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;
}

等待页面

等待页面不需要任何代码隐藏代码,因为它只是一个显示页面,使用 JavaScript 和 XmlHttpRequest 来轮询服务器以查看长时间运行的进程何时完成。本质上,页面会对 CheckStatus.aspx 页面发出客户端请求,然后根据响应采取行动。所有这些都发生在页面不刷新的情况下。同样重要的是要注意,此技术是跨浏览器兼容的,可在 IE5+、Mozilla、Netscape 6+、Firefox 1+ 等中使用。请注意,在撰写本文时,Opera 浏览器不支持 XmlHttpRequest 对象,但有冒险精神的程序员可以使用隐藏的 IFrame 来模拟此功能,就像 Google Suggest 所做的那样。这留给程序员作为练习 =)。

<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>

另请注意,在重定向到确认页面时使用了 document.location.replace();。这有效地从浏览器历史记录中移除了等待页面,这样用户就不会按下“后退”按钮并无意中重新启动进程。

CheckStatus 页面

这确实是一个非常简单的页面。它只是检查 IAsyncResult 是否已完成,并返回一个位(0 或 1)来指示状态。

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();
}

确认页面

一旦长时间运行的进程完成,等待页面将从 CheckStatus 页面获得信号,表明可以继续进行。一旦发生这种情况,等待页面将重定向到确认页面并向用户显示结果 - 这些都是您可以根据需要实现的相当常规的内容。

关注点

鉴于最近对 Google Suggest 和 XmlHttpRequest 对象的所有关注,我认为展示它如何解决现实世界的问题会很有趣。此外,我认为是时候给不起眼的等待页面一些自己的润色了。

历史

  • 2005 年 3 月 17 日 - 发布初始版本。
© . All rights reserved.