处理未经处理的异常 (C#)

作者 :Scott Mitchell

查看或下载示例代码如何下载

当生产中的 Web 应用程序发生运行时错误时,请务必通知开发人员并记录错误,以便在以后的某个时间点对其进行诊断。 本教程概述了 ASP.NET 如何处理运行时错误,并查看一种方法,使自定义代码在未经处理的异常浮升到 ASP.NET 运行时时执行。

简介

当 ASP.NET 应用程序中发生未经处理的异常时,它会浮升到 ASP.NET 运行时,这会引发 Error 事件并显示相应的错误页。 有三种不同类型的错误页:运行时错误黄屏死 (YSOD) ;异常详细信息 YSOD;和自定义错误页。 在 前面的教程中 ,我们已将应用程序配置为对远程用户使用自定义错误页,并将异常详细信息 YSOD 配置为用于本地访问的用户。

使用与站点外观匹配的人工友好自定义错误页优先于默认运行时错误 YSOD,但显示自定义错误页只是综合错误处理解决方案的一部分。 当生产中的应用程序中发生错误时,请务必通知开发人员该错误,以便他们能够发现异常的原因并解决它。 此外,应记录错误的详细信息,以便在以后的时间点检查和诊断错误。

本教程介绍如何访问未经处理的异常的详细信息,以便可以记录这些异常并通知开发人员。 遵循此教程的两个教程探讨了错误日志记录库,经过一些配置后,这些库将自动通知开发人员运行时错误并记录其详细信息。

注意

如果需要以某种唯一或自定义的方式处理未经处理的异常,则本教程中介绍的信息非常有用。 在只需要记录异常并通知开发人员的情况下,可以使用错误日志记录库。 接下来的两个教程概述了两个此类库。

引发事件时Error执行代码

事件为对象提供了一种机制,用于向某个有趣的内容发出信号,并为另一个对象执行代码以响应。 作为一名 ASP.NET 开发人员,你习惯于从事件的角度思考。 如果要在访问者单击特定 Button 时运行某些代码,请为该 Button Click 的事件创建事件处理程序,并将代码放在其中。 鉴于每当发生未经处理的异常时,ASP.NET 运行时都会引发其 Error 事件 ,因此,用于记录错误详细信息的代码将进入事件处理程序。 但是,如何为 Error 事件创建事件处理程序?

事件Error是 类中的HttpApplication许多事件之一,这些事件在请求生存期内在 HTTP 管道的某些阶段引发。 例如,类HttpApplication的事件BeginRequest在每个请求开始时引发;当安全模块标识请求者时,将引发该AuthenticateRequest类的事件。 这些 HttpApplication 事件为页面开发人员提供了在请求生存期内的各个点执行自定义逻辑的方法。

事件的事件处理程序 HttpApplication 可以放在名为 Global.asax的特殊文件中。 若要在网站中创建此文件,请使用名为 Global.asax的全局应用程序类模板向网站的根目录添加新项。

突出显示全局点 A S A X 文件的 Sceenshot。

图 1:添加到 Global.asax Web 应用程序
(单击以查看全尺寸图像)

Visual Studio 创建的文件的内容和结构 Global.asax 因使用的是 Web 应用程序项目 (WAP) 还是网站项目 (WSP) 而略有不同。 使用 WAP 时, Global.asax 实现为两个单独的文件 - Global.asaxGlobal.asax.cs。 该文件 Global.asax@Application 包含引用文件的 .cs 指令;相关事件处理程序在 Global.asax.cs 文件中定义。 对于 WSP,仅创建一个文件 , Global.asax并在块中 <script runat="server"> 定义事件处理程序。

Visual Global.asax Studio 的全局应用程序类模板在 WAP 中创建的文件包括名为 Application_BeginRequestApplication_AuthenticateRequestApplication_Error的事件处理程序,它们分别是事件 BeginRequestAuthenticateRequestError的事件处理程序HttpApplication。 还有名为 Application_StartSession_StartApplication_EndSession_End的事件处理程序,它们是在 Web 应用程序启动时、新会话启动时、应用程序结束和会话结束时分别触发的事件处理程序。 Global.asax Visual Studio 在 WSP 中创建的文件仅Application_Error包含 、Application_StartSession_StartApplication_EndSession_End 事件处理程序。

注意

部署 ASP.NET 应用程序时,需要将 Global.asax 文件复制到生产环境。 在 Global.asax.cs WAP 中创建的文件不需要复制到生产环境,因为此代码已编译到项目的程序集中。

Visual Studio 的全局应用程序类模板创建的事件处理程序并不详尽。 可以通过将事件处理程序命名为 ,为任何 HttpApplication 事件添加事件处理程序 Application_EventName。 例如,可以将以下代码添加到 文件, Global.asax 以便为 AuthorizeRequest 事件创建事件处理程序:

protected void Application_AuthorizeRequest(object sender, EventArgs e)
{
    // Event handler code
}

同样,可以删除全局应用程序类模板创建的任何不需要的事件处理程序。 在本教程中,我们只需要事件的事件处理程序 Error ;请随时从 Global.asax 文件中删除其他事件处理程序。

注意

HTTP 模块 提供了另一种为 HttpApplication 事件定义事件处理程序的方法。 HTTP 模块创建为类文件,可直接放置在 Web 应用程序项目中,也可以将其分离到单独的类库中。 由于可以将其分离到类库中,因此 HTTP 模块为创建 HttpApplication 事件处理程序提供了更灵活且可重用的模型。 Global.asax虽然该文件特定于它所在的 Web 应用程序,但 HTTP 模块可以编译为程序集,此时,将 HTTP 模块添加到网站就像在 文件夹中删除程序集Bin并在 中Web.config注册模块一样简单。 本教程不介绍如何创建和使用 HTTP 模块,但以下两个教程中使用的两个错误日志记录库作为 HTTP 模块实现。 有关 HTTP 模块优势的更多背景信息,请参阅 使用 HTTP 模块和处理程序创建可插入 ASP.NET 组件

检索有关未经处理的异常的信息

此时,我们有一个包含事件处理程序的 Application_Error Global.asax 文件。 执行此事件处理程序时,我们需要将错误通知开发人员并记录其详细信息。 若要完成这些任务,我们首先需要确定引发的异常的详细信息。 使用 Server 对象的 GetLastError 方法 检索导致 Error 事件触发的未经处理的异常的详细信息。

protected void Application_Error(object sender, EventArgs e)
{
    // Get the error details
    HttpException lastErrorWrapper = 
        Server.GetLastError() as HttpException;
}

方法GetLastError返回类型 Exception为 的对象,该对象是.NET Framework中所有异常的基类型。 但是,在上面的代码中,我将返回的 GetLastError Exception 对象转换为 对象 HttpExceptionError如果由于在处理 ASP.NET 资源期间引发了异常而触发事件,则引发的异常将包装在 中HttpException。 若要获取引发 Error 事件的实际异常,请使用 InnerException 属性。 Error如果事件是由于基于 HTTP 的异常(例如对不存在页的请求)引发的,HttpException则会引发 ,但它没有内部异常。

以下代码使用 GetLastErrormessage 检索有关触发 Error 事件的异常的信息,并将 HttpException 存储在名为 的 lastErrorWrapper变量中。 然后,它会将引发异常的类型、消息和堆栈跟踪存储在三个字符串变量中,检查是否 lastErrorWrapper 是触发 Error 事件的实际异常, (基于 HTTP 的异常) ,或者它是否只是处理请求时引发的异常的包装器。

protected void Application_Error(object sender, EventArgs e)
{
    // Get the error details
    HttpException lastErrorWrapper = 
        Server.GetLastError() as HttpException;

    Exception lastError = lastErrorWrapper;
    if (lastErrorWrapper.InnerException != null)
        lastError = lastErrorWrapper.InnerException;

    string lastErrorTypeName = lastError.GetType().ToString();
    string lastErrorMessage = lastError.Message;
    string lastErrorStackTrace = lastError.StackTrace;
}

此时,你已拥有编写将异常详细信息记录到数据库表的代码所需的所有信息。 可以创建一个数据库表,其中包含每个感兴趣的错误详细信息的列(类型、消息、堆栈跟踪等),以及其他有用的信息片段,例如所请求页面的 URL 和当前登录用户的名称。 然后,在 Application_Error 事件处理程序中,将连接到数据库并将记录插入表中。 同样,可以添加代码,通过电子邮件向开发人员发出错误警报。

在接下来的两个教程中介绍的错误日志记录库提供了开箱即用的此类功能,因此无需自行生成此错误日志记录和通知。 但是,为了说明 Error 正在引发事件,以及 Application_Error 事件处理程序可用于记录错误详细信息并通知开发人员,让我们添加代码,以便在发生错误时通知开发人员。

发生未经处理的异常时通知开发人员

当生产环境中发生未经处理的异常时,请务必提醒开发团队,以便他们能够评估错误并确定需要采取的操作。 例如,如果连接到数据库时出错,则需要检查连接字符串,并可能向 Web 托管公司开具支持票证。 如果异常是由于编程错误而发生的,则可能需要添加其他代码或验证逻辑,以防止将来出现此类错误。

命名空间中的System.Net.Mail.NET Framework类可以轻松发送电子邮件。 MailMessage表示电子邮件,并具有 、、FromSubjectBodyAttachmentsTo属性。 SmtpClass用于使用指定的 SMTP 服务器发送MailMessage对象;可以在 中的 Web.config file元素中<system.net>以编程方式或声明方式指定 SMTP 服务器设置。 有关在 ASP.NET 应用程序中发送电子邮件的详细信息,检查文章“从 ASP.NET 网页网站发送Email”和 System.Net.Mail 常见问题解答

注意

元素 <system.net> 包含 发送电子邮件时类 SmtpClient 使用的 SMTP 服务器设置。 Web 托管公司可能具有可用于从应用程序发送电子邮件的 SMTP 服务器。 有关应在 Web 应用程序中使用的 SMTP 服务器设置的信息,请参阅 Web 主机的支持部分。

将以下代码添加到事件处理程序, Application_Error 以在发生错误时向开发人员发送电子邮件:

void Application_Error(object sender, EventArgs e)
{
    // Get the error details
    HttpException lastErrorWrapper = 
        Server.GetLastError() as HttpException;

    Exception lastError = lastErrorWrapper;
    if (lastErrorWrapper.InnerException != null)
        lastError = lastErrorWrapper.InnerException;

    string lastErrorTypeName = lastError.GetType().ToString();
    string lastErrorMessage = lastError.Message;
    string lastErrorStackTrace = lastError.StackTrace;

    const string ToAddress = "support@example.com";
    const string FromAddress = "support@example.com";
    const string Subject = "An Error Has Occurred!";
    
    // Create the MailMessage object
    MailMessage mm = new MailMessage(FromAddress, ToAddress);
    mm.Subject = Subject;
    mm.IsBodyHtml = true;
    mm.Priority = MailPriority.High;
    mm.Body = string.Format(@"
<html>
<body>
  <h1>An Error Has Occurred!</h1>
  <table cellpadding=""5"" cellspacing=""0"" border=""1"">
  <tr>
  <tdtext-align: right;font-weight: bold"">URL:</td>
  <td>{0}</td>
  </tr>
  <tr>
  <tdtext-align: right;font-weight: bold"">User:</td>
  <td>{1}</td>
  </tr>
  <tr>
  <tdtext-align: right;font-weight: bold"">Exception Type:</td>
  <td>{2}</td>
  </tr>
  <tr>
  <tdtext-align: right;font-weight: bold"">Message:</td>
  <td>{3}</td>
  </tr>
  <tr>
  <tdtext-align: right;font-weight: bold"">Stack Trace:</td>
  <td>{4}</td>
  </tr> 
  </table>
</body>
</html>",
        Request.RawUrl,
        User.Identity.Name,
        lastErrorTypeName,
        lastErrorMessage,
        lastErrorStackTrace.Replace(Environment.NewLine, "<br />"));

    // Attach the Yellow Screen of Death for this error   
    string YSODmarkup = lastErrorWrapper.GetHtmlErrorMessage();
    if (!string.IsNullOrEmpty(YSODmarkup))
    {
        Attachment YSOD = 
            Attachment.CreateAttachmentFromString(YSODmarkup, "YSOD.htm");
        mm.Attachments.Add(YSOD);
    }

    // Send the email
    SmtpClient smtp = new SmtpClient();
    smtp.Send(mm);
}

虽然上述代码相当长,但大部分代码会创建显示在发送给开发人员的电子邮件中的 HTML。 代码首先引用HttpException方法 (lastErrorWrapper) 返回GetLastError的 。 请求引发的实际异常通过 lastErrorWrapper.InnerException 检索,并分配给变量 lastError。 类型、消息和堆栈跟踪信息从 lastError 中检索并存储在三个字符串变量中。

接下来,创建一 MailMessage 个名为 的对象 mm 。 电子邮件正文采用 HTML 格式,显示所请求页面的 URL、当前登录用户的名称,以及有关异常的信息 (类型、邮件和堆栈跟踪) 。 类的一个很酷的事情 HttpException 是,可以通过调用 GetHtmlErrorMessage 方法生成用于创建异常详细信息死亡 (YSOD) 的 HTML。 此方法在此处用于检索异常详细信息 YSOD 标记,并将其作为附件添加到电子邮件中。 请注意:如果触发 Error 事件的异常是基于 HTTP 的异常 (例如对不存在页的请求) 则 GetHtmlErrorMessage 方法将返回 null

最后一步是发送 MailMessage。 这是通过创建新 SmtpClient 方法并调用其 Send 方法来完成的。

注意

在 Web 应用程序中使用此代码之前,需要将 和 FromAddress 常量中的ToAddress值从 support@example.com 更改为错误通知电子邮件应发送到和源自的任何电子邮件地址。 还需要在 中的 Web.config部分中指定 SMTP 服务器设置<system.net>。 请咨询 Web 主机提供程序以确定要使用的 SMTP 服务器设置。

每当出现错误时,此代码就已到位,开发人员会收到一封电子邮件,其中汇总了错误并包含 YSOD。 在前面的教程中,我们通过访问Genre.aspx并通过查询字符串传入无效 ID 值(如 Genre.aspx?ID=foo)演示了运行时错误。 访问包含 Global.asax 文件的页面将产生与上一教程相同的用户体验 - 在开发环境中,你将继续看到“异常详细信息黄屏死亡”,而在生产环境中,你将看到自定义错误页。 除了此现有行为之外,还会向开发人员发送电子邮件。

图 2 显示了访问 Genre.aspx?ID=foo时收到的电子邮件。 电子邮件正文汇总了异常信息,而 YSOD.htm 附件显示异常详细信息 YSOD 中显示的内容 (请参阅 图 3) 。

显示发送给开发人员的电子邮件的屏幕截图。

图 2:每当出现未经处理的异常时,开发人员都会收到Email通知
(单击以查看全尺寸图像)

屏幕截图显示电子邮件通知包含异常详细信息 Y S O D 作为附件。

图 3:Email通知包含异常详细信息 YSOD 作为附件
(单击以查看全尺寸图像)

如何使用自定义错误页?

本教程演示如何使用 Global.asax 和 事件处理程序在 Application_Error 发生未经处理的异常时执行代码。 具体而言,我们使用此事件处理程序通知开发人员出现错误;我们可以扩展它,以同时记录数据库中的错误详细信息。 事件处理程序的存在 Application_Error 不会影响最终用户的体验。 他们仍然会看到配置的错误页,无论是错误详细信息 YSOD、运行时错误 YSOD 还是自定义错误页。

使用自定义错误页时,自然会怀疑文件和Application_Error事件是否Global.asax是必需的。 发生错误时,用户会看到自定义错误页,因此为什么我们不能将代码用于通知开发人员,并将错误详细信息记录到自定义错误页的代码隐藏类中? 虽然你当然可以将代码添加到自定义错误页的代码隐藏类,但在使用我们在上一教程中介绍的技术时,你无法访问触发 Error 事件的异常的详细信息。 GetLastError从自定义错误页调用 方法将Nothing返回 。

之所以出现此行为,是因为自定义错误页是通过重定向到达的。 当未经处理的异常到达 ASP.NET 运行时时,ASP.NET 引擎会引发其Error事件 (该事件) 执行Application_Error事件处理程序,然后通过发出 Response.Redirect(customErrorPageUrl)将用户重定向到自定义错误页。 方法 Response.Redirect 使用 HTTP 302 状态代码向客户端发送响应,指示浏览器请求新的 URL,即自定义错误页。 然后,浏览器会自动请求此新页面。 你可以判断自定义错误页是单独请求的,因为浏览器的地址栏更改为自定义错误页 URL, (请参阅 图 4) 。

显示发生错误时浏览器重定向的屏幕截图。

图 4:发生错误时,浏览器会重定向到自定义错误页 URL
(单击以查看全尺寸图像)

最终效果是,当服务器使用 HTTP 302 重定向进行响应时,发生未经处理的异常的请求将结束。 对自定义错误页的后续请求是全新的请求;至此,ASP.NET 引擎已放弃错误信息,并且无法将上一个请求中未经处理的异常与自定义错误页的新请求相关联。 这就是从自定义错误页调用 时返回 null 的原因GetLastError

但是,可以在导致错误的同一请求期间执行自定义错误页。 方法 Server.Transfer(url) 将执行传输到指定的 URL,并在同一请求中对其进行处理。 可以将事件处理程序中的 Application_Error 代码移动到自定义错误页的代码隐藏类,将其 Global.asax 替换为以下代码:

protected void Application_Error(object sender, EventArgs e)
{
    // Transfer the user to the appropriate custom error page
    HttpException lastErrorWrapper = 
        Server.GetLastError() as HttpException;

    if (lastErrorWrapper.GetHttpCode() == 404)
    {
        Server.Transfer("~/ErrorPages/404.aspx");
    }
    else
    {
        Server.Transfer("~/ErrorPages/Oops.aspx");
    }
}

现在,当发生未经处理的异常时, Application_Error 事件处理程序会根据 HTTP 状态代码将控制权转移到相应的自定义错误页。 由于控制已转移,因此自定义错误页可以通过 Server.GetLastError 访问未经处理的异常信息,并且可以通知开发人员错误并记录其详细信息。 调用 Server.Transfer 会阻止 ASP.NET 引擎将用户重定向到自定义错误页。 相反,自定义错误页的内容将作为对生成错误的页面的响应返回。

总结

当 ASP.NET Web 应用程序中发生未经处理的异常时,ASP.NET 运行时将引发 事件 Error 并显示配置的错误页。 可以通过为 Error 事件创建事件处理程序,通知开发人员错误、记录其详细信息或以某种其他方式处理错误。 有两种方法可以创建 HttpApplication 事件事件处理程序,例如 Error:在 Global.asax 文件中或从 HTTP 模块创建事件处理程序。 本教程演示如何在 Global.asax 文件中创建Error事件处理程序,以通过电子邮件通知开发人员错误。

如果需要以某种唯一 Error 或自定义的方式处理未经处理的异常,则创建事件处理程序非常有用。 但是,创建自己的 Error 事件处理程序来记录异常或通知开发人员并不是最有效地利用你的时间,因为已经存在免费且易于使用的错误日志记录库,这些库可在几分钟内设置。 接下来的两个教程将介绍两个此类库。

编程愉快!

深入阅读

有关本教程中讨论的主题的详细信息,请参阅以下资源: