Using .NET 3.5, ASP.NET, Enterprise Library 4.1 Exception Handling and Logging blocks, I wrote a custom exception handler to display a standard error page, as follows:
[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;
}
}
This is called from an exception handling policy like this:
<?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>
It all works fine, except that the error page was being incorporated into whatever markup was displayed at the time the error occurred. I thought that adding response.End()
into the exception handler would solve this problem. It did, but I then observed that, sometimes, ASP.NET throws a ThreadAbortException
while trying to end the response stream. This is the only exception type that cannot be caught and ignored. Grrr! Can anyone tell me:
- Under what conditions ASP.NET would throw a
ThreadAbortException
when trying to end the response stream? - Is there a safer alternative to
response.End()
that I could use? - If not, is there a way of detecting whether the response stream is "end-able" before calling
response.End()
?
EDIT - More Context
The design goal for this code is to make it as simple as possible to add EHAB-style error handling to a web app. I have a custom IHttpModule
that must be added to the web.config
file. In the module's Application_Error
event it calls ExceptionPolicy.HandleException
. The exception handling policy must be configured to call the PageExceptionHandler
class described above. This whole procedure involves only a small amount of XML config, no extra files, and no extra lines of code in the consuming web app.
In fact, the code as it stands seems to work fine. However, in some situations it is desirable to have an explicit catch block in the web app code that calls the exception handling policy directly. This sort of scenario is what does not work properly. I would like a single solution that works under all possible methods of calling it. I do get the impression that it would be a lot simpler if the EHAB was not involved, but unfortunately it is used throughout all our code, and provides so many other benefits that I would prefer to keep it in.
EDIT - Testing Results
I created a test harness to exercise the code in six different ways:
- Writing to the current page's
HttpResponse
directly. - Constructing an exception object and calling
ExceptionPolicy.HandleException(ex, "Top Level")
directly. - Throwing an exception object, catching it, and calling
ExceptionPolicy.HandleException(ex, "Top Level")
in the catch block. - Throwing an exception object and letting the
Page_Error
event callExceptionPolicy.HandleException(ex, "Top Level")
. - Throwing an exception object and letting the
Application_Error
event callExceptionPolicy.HandleException(ex, "Top Level")
. - Throwing an exception object and letting my custom
IHttpModule
class callExceptionPolicy.HandleException(ex, "Top Level")
.
Test results for each method with no code to terminate the response after writing the error page markup:
- Methods 1, 2, 3 - Error page markup combined with the test page markup.
- Methods 4, 5, 6 - Error page markup replaced the test page markup (desired result).
Test results for each method when HttpContext.Current.ApplicationInstance.CompleteRequest
was called:
- Methods 1, 2, 3 - Error page markup combined with the test page markup.
- Methods 4, 5, 6 - Error page markup replaced the test page markup (desired result).
Test results for each method when HttpContext.Current.Response.End
was called:
- Methods 1, 5, 6 - Error page markup replaced the test page markup (desired result).
- Methods 2, 3, 4 - Error page markup replaced the test page markup, but the EHAB throws a
ExceptionHandlingException
twice.
Test results for each method when HttpContext.Current.Response.End
was called, but wrapped in a try
... catch
block:
- Methods 5, 6 - Error page markup replaced the test page markup (desired result).
- Methods 1, 3, 4 - Error page markup replaced the test page markup, but ASP.NET throws a
ThreadAbortException
which is caught and absorbed by the catch block. - Method 2 - Error page markup replaced the test page markup, but I get a
ThreadAbortException
and also twoExceptionHandlingException
s.
It's a ridiculous set of behaviours. :-(