异常处理应用程序块异常在ASP.NET中运行的处理程序无法调用Response.End()

时间:2009-06-26 10:41:04

标签: c# .net asp.net enterprise-library

使用.NET 3.5,ASP.NET,Enterprise Library 4.1异常处理和日志记录块,我编写了一个自定义异常处理程序来显示标准错误页面,如下所示:

[ConfigurationElementType(typeof(CustomHandlerData))]
public class PageExceptionHandler : IExceptionHandler {

    public PageExceptionHandler(NameValueCollection ignore) {
    }

    public Exception HandleException(Exception ex, Guid handlingInstanceID) {

        HttpResponse response = HttpContext.Current.Response;
        response.Clear();
        response.ContentEncoding = Encoding.UTF8;
        response.ContentType = "text/html";
        response.Write(BuildErrorPage(ex, handlingInstanceID));
        response.Flush();
        //response.End(); // SOMETIMES DOES NOT WORK

        return ex;
    }
}

这是从这样的异常处理策略中调用的:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <section name="exceptionHandling" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration.ExceptionHandlingSettings, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
  </configSections>
  <exceptionHandling>
    <exceptionPolicies>
      <add name="Top Level">
        <exceptionTypes>
          <add type="System.Exception, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
            postHandlingAction="None" name="Exception">
            <exceptionHandlers>
              <add logCategory="General" eventId="0" severity="Error" title="Application Error"
                formatterType="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.TextExceptionFormatter, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
                priority="0" useDefaultLogger="false" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging.LoggingExceptionHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
                name="Log Full Details" />
              <add type="PageExceptionHandler, Test, Culture=neutral, PublicKeyToken=null"
                name="Display Error Page" />
            </exceptionHandlers>
          </add>
        </exceptionTypes>
      </add>
    </exceptionPolicies>
  </exceptionHandling>
</configuration>

一切正常,除了错误页面被合并到错误发生时显示的任何标记中。我认为将response.End()添加到异常处理程序中可以解决此问题。它确实如此,但后来我发现,有时,ASP.NET会在尝试结束响应流时抛出ThreadAbortException。这是唯一无法捕获和忽略的异常类型。哎呀!谁能告诉我:

  1. 在尝试结束响应流时ASP.NET会在什么条件下抛出ThreadAbortException
  2. 我可以使用response.End()更安全的替代方案吗?
  3. 如果没有,是否有办法在调用response.End()之前检测响应流是否“可以”?

    编辑 - 更多背景

    此代码的设计目标是尽可能简单地将EHAB样式的错误处理添加到Web应用程序。我有一个必须添加到IHttpModule文件的自定义web.config。在模块的Application_Error事件中,它调用ExceptionPolicy.HandleException。必须将异常处理策略配置为调用上述PageExceptionHandler类。整个过程只涉及少量XML配置,没有额外文件,并且在使用Web应用程序中没有额外的代码行。

    事实上,现在的代码似乎工作正常。但是,在某些情况下,需要在Web应用程序代码中具有直接调用异常处理策略的显式catch块。这种情况是不能正常工作的。我想要一个可以在所有可能的调用方法下工作的解决方案。我的印象是,如果没有参与EHAB会更简单,但不幸的是它在我们所有的代码中使用,并提供了许多其他好处,我宁愿保留它。

    编辑 - 测试结果

    我创建了一个测试工具,以六种不同的方式练习代码:

    1. 直接写入当前页面的HttpResponse
    2. 构建一个异常对象并直接调用ExceptionPolicy.HandleException(ex, "Top Level")
    3. 抛出异常对象,捕获它,并在catch块中调用ExceptionPolicy.HandleException(ex, "Top Level")
    4. 抛出异常对象并让Page_Error事件调用ExceptionPolicy.HandleException(ex, "Top Level")
    5. 抛出异常对象并让Application_Error事件调用ExceptionPolicy.HandleException(ex, "Top Level")
    6. 抛出异常对象并让我的自定义IHttpModule课程调用ExceptionPolicy.HandleException(ex, "Top Level")
    7. 在编写错误页面标记后,没有代码终止响应的每个方法的测试结果:

      • 方法1,2,3 - 错误页面标记与测试页面标记相结合。
      • 方法4,5,6 - 错误页面标记取代了测试页面标记(所需结果)。

      调用HttpContext.Current.ApplicationInstance.CompleteRequest时每种方法的测试结果:

      • 方法1,2,3 - 错误页面标记与测试页面标记相结合。
      • 方法4,5,6 - 错误页面标记取代了测试页面标记(所需结果)。

      调用HttpContext.Current.Response.End时每种方法的测试结果:

      • 方法1,5,6 - 错误页面标记取代了测试页面标记(所需结果)。
      • 方法2,3,4 - 错误页面标记取代了测试页面标记,但EHAB抛出了ExceptionHandlingException 两次

      调用HttpContext.Current.Response.End时的每个方法的测试结果,但包含在try ... catch块中:

      • 方法5,6 - 错误页面标记取代了测试页面标记(所需结果)。
      • 方法1,3,4 - 错误页面标记取代了测试页面标记,但是ASP.NET抛出了ThreadAbortException,它被catch块捕获并吸收。
      • 方法2 - 错误页面标记取代了测试页面标记,但我得到了一个 ThreadAbortException 以及两个ExceptionHandlingException s。

      这是一组荒谬的行为。 : - (

6 个答案:

答案 0 :(得分:2)

查看ELMAH ELMAH上有很多文章和操作方法,只是google或bing for it :-)

<强>优点
+它几乎记录了所有内容 +电子邮件通知
+远程查看错误

我建议使用它并重定向到默认错误页面,正如Ariel所说。

答案 1 :(得分:0)

看看这个Microsoft explanation。它说它适用于ASP.Net 1.1,但我认为它仍然适用于2.0。基本上你应该调用HttpContext.Current.ApplicationInstance.CompleteRequest方法。

另外,我不确定为什么你不能抓住ThreadAbortException - 为什么你不能做像

这样的事情
try {
    // code
} catch (ThreadAbortException)
{
    //do nothing
}

答案 2 :(得分:0)

我处理ASP.net异常的方法总是抓住它,记录它然后重定向到带有一些标准错误消息的通用错误页面(最终用户不关心异常或堆栈跟踪)。 您已经在处理程序之前在策略中记录了异常,因此您可能需要做的只是将响应重定向到错误页面并在那里移动BuildErrorPage逻辑。您可以在查询字符串中传递handlingInstanceId。

答案 3 :(得分:0)

尝试在Global.asax中使用Application_Error来捕获应用程序中任何页面抛出的任何非处理异常。

答案 4 :(得分:0)

这可能有所帮助:http://msdn.microsoft.com/en-us/library/cyayh29d.aspx

具体地 “你的清理代码必须在catch子句或finally子句中,因为系统在finally子句的末尾重新抛出ThreadAbortException,或者如果没有finally子句则在catch子句的末尾重新抛出

您可以通过调用Thread .. ::。ResetAbort方法来阻止系统重新抛出异常。但是,只有在您自己的代码导致ThreadAbortException时才应该这样做。 “

请注意,Response.End和Response.Redirect可能会在某些条件下抛出ThreadAbortException。

答案 5 :(得分:0)

经过大量测试后,我发现只有两种方法可以正常运行。

解决方案1 ​​

根本没有改变当前的架构。全局异常处理程序已按预期工作。如果由于任何原因在代码中需要显式的catch块(例如,在输入表单上呈现验证错误),那么我将告诉开发人员明确地调用Response.End,例如。

try {
    // ...
} catch(Exception ex) {
    if(ExceptionPolicy.HandleException(ex, "Top Level")) {
        throw;
    }
    Response.End();
}

解决方案2

放弃尝试覆盖当前的响应流并执行真正的重定向到错误页面。这适用于所有六种测试工具方法。下一个问题是找到一种方法,这需要对消费项目进行尽可能少的更改,并抽象掉所有脏的细节。

步骤1 - 向项目添加占位符ErrorPage.ashx文件,该文件仅包含对标准网页处理程序类的引用,不包含代码隐藏。

<%@ WebHandler Class="WebApplicationErrorPage" %>

第2步 - 使用会话密钥在PageExceptionHandler课程中重定向到此页面,以传递所有必需的数据。

public Exception HandleException(Exception ex, Guid handlingInstanceID) {

    HttpResponse response = HttpContext.Current.Response;
    HttpSessionState session = HttpContext.Current.Session;

    session["ErrorPageText"] = BuildErrorPage(ex, handlingInstanceID);
    response.Redirect(errorPageUrl, false);

    return ex;
}

步骤3 - 使WebApplicationErrorPage类成为一个非常愚蠢的HTTP处理程序,只写入响应流。

public class WebApplicationErrorPage : IHttpHandler, IReadOnlySessionState {

    public bool IsReusable {
        get {
            return true;
        }
    }

    public void ProcessRequest(HttpContext context) {

        response.Clear();
        response.ContentEncoding = Encoding.UTF8;
        response.ContentType = "text/html";
        response.Write((string) context.Session["ErrorPageText"]);
        response.Flush();
    }
}

<强>结论

我认为我会坚持使用解决方案1,因为它不涉及切碎和更改现有架构(实现解决方案2将比上面提到的代码片段复杂得多)。我将提出解决方案2,以便在其他地方使用。