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

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

[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; } } 

这是从这样的exception处理策略调用的:

    

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

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

编辑 – 更多背景

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

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

编辑 – 测试结果

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

  1. 直接写入当前页面的HttpResponse
  2. 构造一个exception对象并直接调用ExceptionPolicy.HandleException(ex, "Top Level")
  3. 抛出exception对象,捕获它,并在catch块中调用ExceptionPolicy.HandleException(ex, "Top Level")
  4. 抛出exception对象并让Page_Error事件调用ExceptionPolicy.HandleException(ex, "Top Level")
  5. 抛出exception对象并让Application_Error事件调用ExceptionPolicy.HandleException(ex, "Top Level")
  6. 抛出exception对象并让我的自定义IHttpModule类调用ExceptionPolicy.HandleException(ex, "Top Level")

在编写错误页面标记后,每个方法的测试结果都没有代码来终止响应:

  • 方法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时的每个方法的测试结果,但包含在trycatch块中:

  • 方法5,6 – 错误页面标记替换了测试页面标记(期望的结果)。
  • 方法1,3,4 – 错误页面标记替换了测试页面标记,但ASP.NET抛出了一个ThreadAbortException ,它被catch块捕获并吸收。
  • 方法2 – 错误页面标记替换了测试页面标记,但是我得到了一个ThreadAbortException 两个ExceptionHandlingException

这是一组荒谬的行为。 🙁

看看ELMAH 。
ELMAH上有很多文章和操作方法,只是google或bing for it 🙂

好处
+它几乎记录了所有内容
+电子邮件通知
+远程查看错误

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

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

另外,我不确定为什么你不能捕获ThreadAbortException – 为什么你不能做类似的事情

 try { // code } catch (ThreadAbortException) { //do nothing } 

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

尝试使用Global.asax中的Application_Error来捕获应用程序中任何页面抛出的任何不可处理的exception。

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

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

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

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

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

解决方案1

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

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

解决方案2

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

步骤1 – 将占位符ErrorPage.ashx文件添加到项目中,该文件仅包含对标准Web页面处理程序类的引用,而不包含代码隐藏。

 <%@ 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,以便在其他地方使用。