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

ASP.NET 应用程序错误处理

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (38投票s)

2013年5月31日

CPOL

6分钟阅读

viewsIcon

201691

downloadIcon

3246

ASP.NET 中的应用程序错误处理

引言

当我的应用程序中发生未处理的异常时,我希望我的应用程序给用户一个“体面”的响应。无论出现什么错误,我都不希望用户看到 IIS 或 ASP.NET 生成的不友好的技术错误消息。同时,我希望为每一个未处理的异常接收电子邮件通知。

本文介绍了一个简单而全面的解决方案。

问题

当我的应用程序没有配置错误处理时,根据错误的类型,我的用户可能会看到三种不同的错误页面中的任意一种。

如果用户请求一个不存在的静态资源(例如,HTML 或 JPG 文件),那么用户将看到 IIS 生成的默认 HTTP 错误消息。

如果用户请求一个不存在的动态资源(例如,ASPX 文件),那么用户将看到 ASP.NET 为 HTTP 404 错误生成的默认服务器错误消息。

如果应用程序中发生未处理的异常,那么用户将看到 ASP.NET 为 HTTP 500 错误生成的默认服务器错误消息。

ASP.NET Web 应用程序开发人员有时将这些称为“蓝屏死机”(BSOD)和“黄屏死机”(YSOD)。

解决方案

当用户在我的应用程序中看到错误消息时,我希望错误消息与我的应用程序的布局和风格匹配,并且我希望每条错误消息都遵循相同的布局和风格。

步骤 1:集成管道模式

作为第一步,我将我的应用程序设置为使用配置为集成托管管道模式的应用程序池。

Microsoft Internet Information System (IIS) 6.0 版本(及之前的版本)将 ASP.NET 集成到 ISAPI 扩展中,并与自己的 HTTP 请求处理模型一起。实际上,这提供了两个独立的服务器管道:一个用于本机组件,一个用于托管组件。托管组件完全在 ASP.NET ISAPI 扩展中执行 — 并且仅针对专门映射到 ASP.NET 的请求。IIS 7.0 及更高版本将这两个管道集成起来,以便本机和托管模块提供的服务适用于所有请求,而不管 HTTP 处理程序是什么。

有关集成管道模式的更多信息,请参阅以下 Microsoft 文章。

步骤 2:应用程序配置设置

接下来,我为我的应用程序添加 404 和 500 错误代码的错误页面,并更新应用程序配置文件 (web.config) 中的设置,指示 IIS 为这些错误代码使用我的自定义页面。

<system.webServer>
  <httpErrors errorMode="Custom" existingResponse="Replace">
    <remove statusCode="404"/>
    <remove statusCode="500"/>
    <error statusCode="404" responseMode="ExecuteURL"
    path="/Pages/Public/Error404.aspx"/>
    <error statusCode="500" responseMode="ExecuteURL"
    path="/Pages/Public/Error500.aspx"/>
  </httpErrors>
</system.webServer>

现在,当用户请求一个不存在的静态资源时,用户将看到我的自定义页面生成的错误消息。

同样,如果用户请求一个不存在的动态资源,那么用户将看到我的自定义页面生成的错误消息。

最后,如果应用程序中发生未处理的异常,那么用户将看到我的自定义页面生成的错误消息。

步骤 3:异常详细信息

我的问题的第一部分现在已经解决了:用户看到的错误消息在与我的应用程序布局和风格一致的页面中呈现,并且错误消息本身是一致的,无论未处理的异常的根本原因是什么。

然而,在此配置中,当发生未处理的异常并且 IIS 执行 Error500.aspx 时,该页面后面的代码没有异常本身的详细信息。当发生未处理的异常时,我需要一个崩溃报告保存到服务器的文件系统并以电子邮件发送给我。崩溃报告需要包含异常详细信息和堆栈跟踪,以便我能找到并修复错误的根本原因。

一些文章建议我可以使用 `Server.GetLastError` 来识别未处理的异常,但我每次尝试这样做时都会得到一个 `null` 值。

Exception ex = HttpContext.Current.Server.GetLastError(); // <-- Returns null in Error500.aspx

注意:我未能从 Microsoft 的文档中找到对这一点清楚的解释。(如果您能为我指明解释此行为的文档,请给我留言。)

我可以通过添加一个具有应用程序错误事件处理程序的 HTTP 模块来解决这个问题。例如,在将以下代码添加到 Global.asax 后,我的自定义错误页面就可以访问当前缓存中的异常详细信息。

protected void Application_Error(object sender, EventArgs e)
{
    Exception ex = HttpContext.Current.Server.GetLastError();
    CrashReport report = CrashReporter.CreateReport(ex, null);
    HttpContext.Current.Cache[Settings.Names.CrashReport] = report;
}

重要的是要注意,如果我在事件处理程序的末尾添加代码调用 `Server.ClearError()`,我的自定义错误消息将不会显示给用户(默认的服务器错误消息也不会)。事实上,如果我在这里调用 `ClearError()`,那么错误消息将变成一个空白页,渲染到浏览器的输出中没有任何 HTML。请记住,在此配置中,事件处理程序的目的是将异常详细信息存储在当前缓存(或会话状态)中,以便 Error500.aspx 页面可以访问它。目的不是处理异常本身,这就是为什么在这里不清除错误的原因。

注意:参考我之前的观点,如果我在这里没有清除错误,因为这是确保我的自定义错误页面被执行所必需的,那么为什么自定义错误页面中的 `Server.GetLastError()` 调用会返回 `null` 值就并不明显了。如果您对此有解释,请发表评论。

改进解决方案

我的解决方案需要将崩溃报告写入文件系统(这样我们就有了事件的永久记录),并且需要发送电子邮件通知(这样我们就能立即收到事件的警报)。在任何给定时间,我的公司都在为各种客户积极开发几十个应用程序,因此可重用的解决方案很重要。

添加到 Global.asax 的代码不易跨多个应用程序重用,因此我创建了一个 HTTP 模块(即继承自 `System.Web.IHttpModule` 的类),然后可以将其添加到库中,并从许多不同的应用程序中引用它。

为了使此解决方案正常工作,我将以下设置添加到我的 Web 应用程序配置文件 (Web.config) 的 system.webServer 元素中。

<modules>
    <add name="ApplicationErrorModule" type="Demo.Classes.ApplicationErrorModule" />
</modules>

用于设置应用程序错误处理的代码很简单。

public void Init(HttpApplication application)
{
    application.Error += Application_Error;
}

private void Application_Error(Object sender, EventArgs e)
{
    if (!Settings.Enabled)
        return;

    Exception ex = HttpContext.Current.Server.GetLastError();

    if (UnhandledExceptionOccurred != null)
        UnhandledExceptionOccurred(ex);

    ExceptionOccurred(ex);
}

处理异常本身的代码与我最初添加到全局应用程序事件处理程序中的代码基本相同,但在这里我添加了代码来将崩溃报告保存到文件系统,并将一份副本发送给我。

private static void ExceptionOccurred(Exception ex)
{
    // If the current request is itself an error page 
    // then we need to allow the exception to pass through.

    HttpRequest request = HttpContext.Current.Request;
    if (Regex.IsMatch(request.Url.AbsolutePath, ErrorPagePattern))
        return;

    // Otherwise, we should handle the exception here

    HttpResponse response = HttpContext.Current.Response;
    CrashReport report = new CrashReport(ex, null);

    // Save the crash report in the current cache 
    // so it is accessible to my custom error pages

    if (HttpContext.Current.Cache != null)
        HttpContext.Current.Cache[Settings.Names.CrashReport] = report;

    // Save the crash report on the file system

    String path = SaveCrashReport(report, request, null);

    // Send the crash report to the programmers

    SendEmail(report, path);

    // Write the crash report to the browser 
    // if there is no replacement defined for the HTTP response

    if (!ReplaceResponse)
    {
        HttpContext.Current.Server.ClearError();

        try
        {
            response.Clear();
            response.StatusCode = 500;
            response.StatusDescription = "Server Error";
            response.TrySkipIisCustomErrors = true;
            response.Write(report.Body);
            response.End();
        }
        catch { }
    }
}

此函数中的最后一部分尤其重要。如果出于某种原因,我忘记在 `webserver` 配置元素中包含 `httpErrors` 部分,那么我希望我的崩溃报告正文显示在浏览器中,而不是 ASP.NET 在发生服务器错误时显示的默认“黄屏死机”(YSOD)。

关注点

关于 ASP.NET 应用程序错误处理主题有许多优秀的文章,也有许多优秀的产品有助于解决方案的开发。这里仅提供一些更多信息的参考。

历史

2013 年 6 月 1 日:当 BaseErrorPage 在 PostBack 请求上加载时,响应被指定为 HTTP 状态码 200 (OK)。这使得错误页面上的“提交快速错误报告”功能可用。

2013 年 6 月 5 日:修改了代码,使用会话安全的密钥保存未处理异常的崩溃报告。这纠正了多个并发用户同时遇到不同异常的场景。

© . All rights reserved.