如何以对用户友好的方式显示网络传输或服务器程序运行时产生的异常,一直是Web应用程序开发时需要考虑的一个很重要的问题。在传统的Web开发模型中,我们可以指定一个专门以用户友好的方式显示异常的页面。对于这个功能,ASP.NET以及IIS中已经有其内建的支持。而对于Ajax类型的应用程序来讲,一个页面就是一个程序;若贸然将用户引导至另一个页面,即另一个程序,似乎显得有些唐突。
幸运的是,ASP.NET AJAX考虑到了这一点,并提供了一整套针对异步回调过程中发生的异常处理机制,包括在服务器端对异常进行捕获并加以修饰,然后在同一个页面中以局部更新的方式显示出来等。
让我们通过一个示例程序学习如何使用ASP.NET AJAX内建的异常处理方法。
首先,创建一个ASP.NET页面,并在其中添加一个服务器端Button控件,代码如下:
<asp:Button ID="btnInvokeBadMethod" runat="server" Text="Invoke Bad Method" />
然后在Visual Studio的页面设计器中双击这个按钮,并在生成的事件处理函数中抛出一个异常。该事件处理函数的代码如下:
protected void btnInvokeBadMethod_Click(object sender, EventArgs e)
{
throw new Exception("Hey, I want to tell you the Administrator's password of this server is XXXX!");
}
这真是个愚蠢的方法,居然把这样重要的信息直接显示给了用户。但谁又知道呢?在某些粗心的程序员开发的真实程序中,往往数据库的连接字符串就在某些SQL异常信息中被这样显示了出来。
在浏览器中查看一下该页面,如图3-15所示。
图3-15 将抛出异常的页面
点击页面上的那个按钮。噢,不幸的事情终于发生了,用户将看到如此机密的信息(见图3-16)。
图3-16 页面中显示了异常的详细信息
虽然这并不是Ajax应用,点击该按钮仍然导致了一次整页的回送,但它至少测试了该按钮可以“正常”工作(如我们所愿地抛出异常)。在开发过程中不断地进行阶段性的测试也是一个非常好的习惯。
下面让我们使用ASP.NET AJAX将该按钮改成异步回送模式,即点击该按钮将不会导致整页的回送。首先在页面中添加一个ScriptManager控件。代码如下:
<asp:ScriptManager ID="ScriptManager1" AllowCustomErrorsRedirect="false" runat="server" />
这里使用了ScriptManager控件的一个非常重要的属性:AllowCustomErrorsRedirect,该属性为布尔类型,默认值为true,表示在异步更新发生异常时是否沿用Web.config中<customErrors>节中的设定。Web.config的<customErrors>节中可以指定应用程序级别的错误处理页面,这将通过重定向至某个专门显示异常的页面来实现。而对于Ajax程序而言,页面跳转则是应该竭力避免的,因此一般来讲,最好将其设置为false。
然后在页面的Page_Load()方法中加入以下代码:
protected void Page_Load(object sender, EventArgs e)
{
ScriptManager.GetCurrent(this).RegisterAsyncPostBackControl(btnInvokeBadMethod);
}
上面这段代码中通过ScriptManager.GetCurrent(this)得到当前页面中ScriptManager的实例(ScriptManager是Singleton模式的组件),然后调用其RegisterAsyncPostBackControl()方法将btnInvokeBadMethod按钮注册为支持异步回送的控件。关于GetCurrent()和Register- AsyncPostBackControl()方法,将在稍后介绍。
再测试一下页面:点击按钮后不会有整页回送出现,这也正是我们所期望的“Ajax”行为。待服务器端执行结果返回之后,将弹出一个JavaScript提示对话框(见图3-17),虽然错误信息仍旧愚蠢地将所有秘密都告诉给了用户,但至少没有让用户毫不知情地等下去。
图3-17 由异步回送控件引发的异常的默认行为
现在我们首先来过滤异常中的敏感信息。按照如下代码修改ScriptManager的定义,注意其中粗体部分:
<asp:ScriptManager ID="ScriptManager1" AllowCustomErrorsRedirect="false" OnAsyncPost BackError="ScriptManager1_AsyncPostBackError" runat="server" />
可以看到,通过OnAsyncPostBackError="ScriptManager1_AsyncPostBackError"这一句指定了ScriptManager的AsyncPostBackError事件的处理函数为ScriptManager1_ AsyncPostBackError()。AsyncPostBackError事件将在异步回送引发异常时触发。在该事件的处理函数中,我们即可分析异常产生的原因并根据需要过滤敏感信息。
ScriptManager1_AsyncPostBackError()的代码如下,注意其中粗体部分:
protected void ScriptManager1_AsyncPostBackError(object sender, AsyncPostBackError EventArgs e)
{
if (e.Exception.Message.Contains("password"))
{
ScriptManager1.AsyncPostBackErrorMessage = "This application cannot complete your request, please try again.";
}
else
{
ScriptManager1.AsyncPostBackErrorMessage = e.Exception.Message;
}
}
上述代码逻辑非常简单:判断异常消息中是否包含敏感信息"password",如果包含,那么使用另外一条笼统的描述将其替换掉,并赋值给ScriptManager的AsyncPostBackError- Message属性。AsyncPostBackErrorMessage属性表示了异步回送过程中发生的异常将显示出的消息,我们也可在ScriptManager的声明中设置这个属性,注意其中粗体部分:
<asp:ScriptManager ID="ScriptManager1" AllowCustomErrorsRedirect="false" AsyncPostBackErrorMessage="Error occured!" runat="server" />
回到该示例程序中,这时在页面中再次点击引发异常的按钮,仍然是JavaScript提示对话框,不过敏感信息已经被过滤掉了,如图3-18所示。
图3-18 过滤掉异常中包含的敏感信息
虽然完成了要求,但这种JavaScript形式的提示对话框似乎并不那么漂亮、友好。接下来的一步就是把异常信息显示在页面中的一个HTML <div>元素中,这样就可用丰富的CSS样式对其进行修饰。
在页面中声明一个<div>元素,并在其中定义显示异常的模板:
<div id="errorMessage">
<h3>Error Occurs</h3>
<p>
An exception has occured on the server:
</p>
<p>
<span id="errorMessageLabel" runat="server"></span>
</p>
<p>
<input id="okButton" type="button" value="OK" runat="server" οnclick="javascript :onOK();" />
</p>
</div>
记住这个<div>的ID以及ID为errorMessageLabel的<span>和ID为okButton的<input>,它们将在不久后用到。与这段HTML代码相关的CSS定义如下,非常简单,这里不赘:
*
{
font-family: Tahoma;
}
#errorMessage
{
width: 300px;
height: 150px;
padding: 10px;
border: solid 3px black;
background: #ffd;
text-align: left;
display: none;
position: absolute;
left: 50px;
top: 50px;
}
接下来是一些客户端JavaScript代码,在页面的任何位置中声明均可。首先是pageLoad()函数:
function pageLoad()
{
Sys.WebForms.PageRequestManager.getInstance().add_endRequest(onEndRequest);
}
这里我们就用到了ASP.NET AJAX的客户端PageRequestManager对象。上述pageLoad()函数中通过Sys.WebForms.PageRequestManager.getInstance()得到该对象的实例,然后为其endRequest事件添加了名为onEndRequest()事件处理函数。PageRequestManager对象的endRequest事件将在异步回送返回时触发。在该事件的处理函数中,我们即可访问到回送过程中可能出现的异常的详细信息。关于PageRequestManager对象,将在第Ⅱ卷中详细介绍。
接下来声明onEndRequest()事件处理函数,代码如下:
function onEndRequest(sender, e)
{
// 如果有异常发生。
if(e.get_error())
{
// 显示异常模板。
$get("errorMessage").style.display = "block";
// 设置异常模板中的异常详细信息。
$get("errorMessageLabel").innerHTML = e.get_error().description;
// 告诉PageRequestManager异常已经处理过,无需再使用默认的JavaScript对话框显示。
e.set_errorHandled(true);
}
}
上述代码中已经有了详细的注释,无需过多解释。其中用到的各种ASP.NET AJAX客户端语法将在第Ⅱ卷中详细介绍,这里我们只要了解其基本含义就够了。
异常模板中还定义了一个ID为okButton的<input>按钮,该按钮的click事件处理函数为onOK(),所执行的工作就是隐藏异常模板,其代码如下:
function onOK()
{
$get("errorMessage").style.display = "none";
}
让我们看看最终的显示错误信息的界面吧,如图3-19所示。
图3-19 更加友好地显示异常
噢,这种效果简直让人难以置信!相对于图3-18,这两种界面简直是天壤之别!当用户点击OK按钮后,错误信息将消失,页面也将变回图3-15中的样子。无论是用户还是开发者,想必对此都会非常满意吧!