ASP.NET 中的错误处理






4.56/5 (37投票s)
2005年6月5日
9分钟阅读

470684

5233
本文从使用 customErrors 部分的用户重定向设置开始,然后转到不同范围内的异常处理。解释了如何防止递归循环,处理解析器错误,以及适当的基类错误处理(包括内部处理)。源代码演示了所说明的概念。
引言
当 ASP.NET 应用程序中发生错误时,它们要么被处理,要么未处理地传播到更高的范围。当未处理的异常传播时,用户可以使用不同的 ASP.NET 配置设置重定向到错误页面。然而,可以通过处理抛出的异常来首先防止这种重定向。因此,ASP.NET 中的错误处理可以分为两个独立的逻辑:
- 当错误未处理时,将用户重定向到错误页面。
- 当抛出异常时,处理异常。
将用户重定向到错误页面
当错误未处理时,我们可以在两个不同的范围指定用户应该重定向到哪个页面:
- 页面级别(适用于单个页面内发生的错误)。
- 应用程序级别(适用于应用程序中任何地方发生的错误)。
页面级别
在 webform 中使用 errorPage
属性。
此属性定义了当该特定页面中发生未处理的异常时,用户应该重定向到的页面。例如,
<%@ Page language="c#" Codebehind="WebForm1.aspx.cs"
AutoEventWireup="false" Inherits="WebTest.WebForm1"
errorPage="/WebTest/ErrorPages/PageError.html"%>
errorPage
属性映射到 Page.ErrorPage
属性,因此可以通过编程方式设置。该值可以选择包含查询字符串参数。如果未添加任何参数,ASP.NET 会自动添加一个名为 aspxerrorpath
的参数。此参数将保存该页面的相对 URL 值,以便错误页面能够确定是哪个页面导致了错误。
如果在此属性(或属性)中指定了值并且页面中发生未处理的异常,则 Page
类将自动执行重定向到指定页面。如果未指定值,则假定该异常未处理,并将其包装在新的 HttpUnhandledException
中,然后抛出,将其传播到下一个更高级别。
应用程序级别
在 web.config 中使用 customErrors
部分。
此部分允许您指定当未处理的异常在应用程序级别传播时,用户应该重定向到的错误页面。此部分为默认错误和 HTTP 状态码错误指定错误页面。
<customErrors mode="On" defaultRedirect="/WebTest/ErrorPages/AppError.html">
<error statusCode="404" redirect="/WebTest/ErrorPages/404.html" />
</customErrors>
mode
属性指定是显示用户定义的自定义错误页面还是 ASP.NET 错误页面。此属性支持三个值:
RemoteOnly
- 所有远程用户都会显示自定义错误页面。具有丰富错误信息的 ASP.NET 错误页面仅显示给本地用户。On
- 始终显示自定义错误页面,除非未指定。如果未定义自定义错误页面,则会显示一个 ASP.NET 错误页面,其中描述了如何启用远程查看错误。Off
- 不显示自定义错误页面。相反,将始终显示 ASP.NET 错误页面,其中包含丰富的错误信息。
向用户提供超出所需的信息是一个糟糕的主意。ASP.NET 错误页面描述了不应暴露的技术细节。因此,理想情况下,mode
属性不应设置为 Off
。
defaultRedirect
属性指定通用错误页面的路径。此页面通常会有一个链接,让用户返回主页或再次执行请求。
每个 error
元素定义特定于特定 HTTP 状态码的重定向。例如,如果错误是 404(文件未找到),则可以将错误页面设置为 FileNotFound.htm。您可以在 customErrors
部分中添加任意数量的错误元素,每个元素指定一个状态码和相应的错误页面路径。如果 ASP.NET 找不到与状态码对应的任何特定错误元素,它将使用 defaultRedirect
属性中指定的值。
注释
- 页面级别(
errorPage
属性)中指定的设置将覆盖customErrors
部分中指定的设置。原因是因为页面中的错误将首先由Page
类处理,这可能会阻止异常传播到应用程序级别。只有当Page
类未能处理异常时,customErrors
中设置的值才会生效。 - 上述所有设置仅适用于针对 ASP.NET 文件发出的请求。更具体地说,这些设置仅适用于扩展名映射到 aspnet_isapi 的文件的请求。例如,如果您请求一个不存在的 ASP 或 JPG 文件(未映射到 aspnet_isapi 的扩展名),那么这些设置将不起作用,并将显示 IIS 中指定的标准错误页面。要修改此行为,请将所需扩展名映射到 aspnet_isapi 或修改 IIS 中指定的自定义错误页面。
异常处理
您可以在不同的级别处理异常。
- 本地(方法级别),可以抛出异常。
- 通过处理
Page.Error
事件的页面级别。 - 通过处理
HttpApplication.Error
事件的应用程序级别。 - 通过处理
HttpApplication.Error
事件的 HTTP 模块级别。
本地错误处理
将可能抛出异常的代码包装在 try
-catch
-finally
块中。
如果您可以从异常中恢复,则在 catch
块中处理它。如果异常无法在本地恢复,则通过抛出它让异常传播到更高级别。如果异常无法在本地恢复,但可以提供额外信息,则将异常与新信息一起包装并抛出新异常。当您使用自定义异常时,会使用此方法。将清理代码放在 finally
块中。
在 MSDN 中查找有关异常处理最佳实践的更多信息。
注意:您捕获和抛出的异常越多,您的应用程序运行速度就越慢。这在 Web 应用程序中更为显著。
页面级别
将处理程序附加到 Page.Error
事件。在 C# 中,您必须在 Page_Load
方法中自己编写事件连接代码。
当页面中发生未处理的异常时,Page
类的 Error
事件将被触发。
通常,您在此处理程序中执行的第一个操作是使用 Server.GetLastError
方法获取抛出的异常。此方法将返回对最后抛出的 Exception
对象的引用。
获取 Exception
对象后,您需要将用户重定向到错误页面。我们可以通过使用 Page
的 errorPage
属性(设计时)或使用 Page.ErrorPage
属性(运行时)让 ASP.NET 执行重定向。显然,这里的选择是在事件处理程序中使用 Page.ErrorPage
属性以编程方式设置值。
private void WebForm1_Error(object sender, EventArgs e)
{
// Get the last exception thrown
Exception ex = Server.GetLastError();
// Do something with the exception like logging etc.
// Set the error page
this.ErrorPage = "/ErrorHandling/ErrorPages/BaseError.html";
}
如果您不指定错误页面,则异常会被包装在 HttpUnhandledException
对象中并传播。如果您不想包装异常,则只需抛出最后一个异常,这将强制立即传播,从而避免任何干预。但是,这也将阻止 ASP.NET 将用户重定向到特定页面。换句话说,如果您要抛出最后一个错误(或任何异常),设置错误页面将无效。
private void BasePage_Error(object sender, EventArgs e)
{
// Get the last exception thrown
Exception ex = Server.GetLastError();
// Do something with the exception like logging etc.
// The statement below has no significance - it's as good as commented
this.ErrorPage = "/ErrorHandling/ErrorPages/BaseError.html";
// Throw the error to prevent wrapping
throw ex;
}
为了减少冗余代码,您可以定义一个基 Web 窗体页面,该页面定义 Page.Error
事件处理程序并在构造函数中连接代码,然后使所有 Web 窗体页面都从该基页面派生。这将省去您在每个 Web 窗体中编写错误处理程序的麻烦。
应用程序级别
将事件处理程序附加到 Application.Error
事件。
当未处理的异常离开页面时,它会传播到应用程序级别,这将触发此事件。
在应用程序错误处理程序中,您需要做两件事。
- 使用
Server.GetLastError
获取最后抛出的异常。 - 使用
Server.ClearError
清除错误,以告知 ASP.NET 您已处理此错误。
如果您不清除错误,则异常将继续传播。然而,由于没有更高级别的范围可以捕获异常,ASP.NET 只能强制处理它。ASP.NET 处理异常的方式取决于我们之前看到的 customErrors
部分中指定的设置。如果未定义任何设置,ASP.NET 将使用默认设置并显示臭名昭著的“黄色”错误页面。
HTTP 模块级别
除了在 global.asax 中处理应用程序错误外,还可以通过附加一个 HTTP 模块来处理异常,该模块将有一个附加到 Application.Error
事件的处理程序。此方法将在相应的应用程序处理程序被调用之前触发。如果您的多个项目具有相同的全局错误处理实现,则这种实现将是有益的。在这种情况下,您可以创建一个模块并将其附加到您拥有的每个 Web 应用程序。
我们在页面和应用程序处理程序中看到的所有要点也适用于模块处理程序。
重要说明
防止无限递归
如果错误处理代码中发生错误,将导致无限递归循环,很快就会拖垮您的服务器。发生这种情况的原因是,新异常会再次触发错误事件,进而将控制重定向到处理程序,从而导致抛出另一个异常,形成无限循环。
如果错误页面本身抛出异常,也可能发生这种情况。为了应对这种可能性,将错误页面设置为静态是一个好主意。
在使用 Server.Transfer
或 Response.Redirect
重定向到错误页面时也可能发生错误,可能是由于路径无效。为了解决这种情况,我们可以将重定向代码包装在 try
-catch
块中。如果重定向失败,那么我们除了设置响应代码并完成响应(使用 Response.StatusCode
属性和 HttpApplication.CompleteResponse
方法)之外,别无他法。然后,这将由 customErrors
部分中指定的设置处理。
解析器错误
解析器错误是由于 aspx 页面中无效标签(或类似原因)引起的。这些错误通常是 HttpParseException
类型。此类错误不会被页面级别处理程序捕获,因为页面解析发生在 ASP.NET 为 aspx 页面创建程序集之前。换句话说,解析器错误是在 ASP.NET 读取 aspx 文件并尝试创建其程序集时抛出的,因此远早于相应类型的创建。因此,此类错误必须在应用程序范围内处理。
异常日志记录和响应时间
用户需要尽快获得响应。在实现方面,这意味着当发生错误时,错误恢复过程应该快速,并且用户应该尽快被重定向或告知错误。如果异常要记录到文件或其他介质,则可能需要时间,从而导致响应缓慢。在这方面,将异常日志记录作为异步过程是一个好主意。
源代码
源代码在 VS.NET 2003 中,虚拟目录名为 ErrorHandling。代码演示了本文中提到的大部分实现。其中一些项目需要您取消注释并重新构建,如相应部分所述。