Controlador de excepciones El controlador de excepciones que se ejecuta en ASP.NET no puede llamar a Response.End ()

Usando los bloques .NET 3.5, ASP.NET, Enterprise Library 4.1 Manejo de excepciones y registro, escribí un controlador de excepciones personalizado para mostrar una página de error estándar, de la siguiente manera:

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

Esto se llama desde una política de manejo de excepciones como esta:

    

Todo funciona bien, excepto que la página de error se incorporó a cualquier marca que se mostró en el momento en que se produjo el error. Pensé que agregar response.End() en el controlador de excepciones resolvería este problema. Lo hizo, pero luego observé que, a veces, ASP.NET lanza una ThreadAbortException al intentar finalizar la secuencia de respuesta. Este es el único tipo de excepción que no se puede detectar ni ignorar. Grrr! Alguien me puede decir:

  1. ¿Bajo qué condiciones ASP.NET lanzaría una ThreadAbortException al intentar finalizar la secuencia de respuesta?
  2. ¿Existe una alternativa más segura a la response.End() que podría usar?
  3. Si no es así, ¿hay una manera de detectar si el flujo de respuesta es “capaz de finalizar” antes de llamar a response.End() ?

EDITAR – Más contexto

El objective de diseño de este código es que sea lo más sencillo posible agregar el manejo de errores de estilo EHAB a una aplicación web. Tengo un IHttpModule personalizado que se debe agregar al archivo web.config . En el evento Application_Error del módulo, llama a ExceptionPolicy.HandleException . La política de manejo de excepciones debe estar configurada para llamar a la clase PageExceptionHandler descrita anteriormente. Todo este procedimiento implica solo una pequeña cantidad de configuración XML, sin archivos adicionales y sin líneas de código adicionales en la aplicación web de consumo.

De hecho, el código actual parece funcionar bien. Sin embargo, en algunas situaciones es deseable tener un bloque de captura explícito en el código de la aplicación web que llame directamente a la política de manejo de excepciones. Este tipo de escenario es lo que no funciona correctamente. Me gustaría una solución única que funcione bajo todos los métodos posibles de llamarlo. Me da la impresión de que sería mucho más simple si el EHAB no participara, pero desafortunadamente se usa en todo nuestro código, y proporciona tantos otros beneficios que preferiría mantenerlo.

EDITAR – Resultados de las pruebas

Creé un arnés de prueba para ejercer el código de seis maneras diferentes:

  1. Escribiendo directamente a la HttpResponse la página actual.
  2. Construyendo un objeto de excepción y llamando a ExceptionPolicy.HandleException(ex, "Top Level") directamente.
  3. Lanzar un objeto de excepción, capturarlo y llamar a ExceptionPolicy.HandleException(ex, "Top Level") en el bloque catch.
  4. Lanzar un objeto de excepción y permitir que el evento Page_Error llame a ExceptionPolicy.HandleException(ex, "Top Level") .
  5. Lanzar un objeto de excepción y permitir que el evento Application_Error llame a ExceptionPolicy.HandleException(ex, "Top Level") .
  6. Lanzar un objeto de excepción y dejar que mi clase IHttpModule personalizada llame a ExceptionPolicy.HandleException(ex, "Top Level") .

Resultados de prueba para cada método sin código para terminar la respuesta después de escribir la marca de página de error:

  • Métodos 1, 2, 3 – Marcado de página de error combinado con el marcado de página de prueba.
  • Métodos 4, 5, 6 – La marca de página de error reemplazó la marca de página de prueba (resultado deseado).

Resultados de prueba para cada método cuando se llamó a HttpContext.Current.ApplicationInstance.CompleteRequest :

  • Métodos 1, 2, 3 – Marcado de página de error combinado con el marcado de página de prueba.
  • Métodos 4, 5, 6 – La marca de página de error reemplazó la marca de página de prueba (resultado deseado).

Resultados de prueba para cada método cuando se llamó a HttpContext.Current.Response.End :

  • Métodos 1, 5, 6 – La marca de página de error reemplazó la marca de página de prueba (resultado deseado).
  • Métodos 2, 3, 4: la marca de página de error reemplazó la marca de página de prueba, pero la EHAB lanza una ExceptionHandlingException dos veces .

Resultados de prueba para cada método cuando se llamó a HttpContext.Current.Response.End , pero envuelto en un bloque trycatch :

  • Métodos 5, 6 – La marca de página de error reemplazó la marca de página de prueba (resultado deseado).
  • Métodos 1, 3, 4: la marca de página de error reemplazó a la marca de página de prueba, pero ASP.NET lanza una ThreadAbortException que es atrapada y absorbida por el bloque catch.
  • Método 2: la marca de página de error reemplazó la marca de la página de prueba, pero obtengo una ExceptionHandlingException ThreadAbortException y también dos ExceptionHandlingException s.

Es un conjunto de comportamientos ridículos. 🙁

Echa un vistazo a ELMAH .
Hay muchos artículos y consejos sobre ELMAH, solo buscalo en google o bing 🙂

Ventajas
+ Registra casi todo.
+ Notificación por correo electrónico
+ Visualización remota de errores.

Yo sugeriría usar eso y redirigir a una página de error predeterminada, como mencionó Ariel.

Echa un vistazo a esta explicación de Microsoft . Dice que se aplica a ASP.Net 1.1, pero creo que todavía se aplica en 2.0. Básicamente, debería llamar al método HttpContext.Current.ApplicationInstance.CompleteRequest lugar.

Además, no estoy seguro de por qué no puedes captar la ThreadAbortException , ¿por qué no puedes hacer algo así?

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

Mi enfoque para manejar las excepciones de ASP.net siempre fue capturarlo, registrarlo y luego redirigirlo a una página de error genérica con algún mensaje de error estándar (el usuario final no se preocupará por las excepciones o los seguimientos de la stack). Ya ha registrado la excepción en su política antes de su controlador, por lo que quizás todo lo que tiene que hacer es una redirección de respuesta a una página de error y mover la lógica BuildErrorPage allí. Puede pasar el handlingInstanceId en la cadena de consulta.

Intente usar Application_Error en Global.asax para capturar cualquier excepción no manejada lanzada por cualquier página en su aplicación.

Esto puede ayudar: http://msdn.microsoft.com/en-us/library/cyayh29d.aspx

Específicamente “Su código de limpieza debe estar en la cláusula catch o en la cláusula finally, porque una excepción ThreadAbortException es eliminada por el sistema al final de la cláusula finally, o al final de la cláusula catch si no hay una cláusula finally .

Puede evitar que el sistema vuelva a generar la excepción llamando al método Thread .. ::. ResetAbort. Sin embargo, debe hacer esto solo si su propio código causó la excepción ThreadAbortException. ”

Tenga en cuenta que Response.End y Response.Redirect pueden lanzar una ThreadAbortException bajo ciertas condiciones.

Después de muchas pruebas, he encontrado solo dos enfoques que funcionan sin error.

Solución 1

Ningún cambio a la architecture actual en absoluto. El controlador de excepciones global ya funciona como se desea. Si se necesita un bloque de captura explícito en el código por algún motivo (por ejemplo, para representar errores de validación en un formulario de entrada), les diré a los desarrolladores que llamen a Response.End explícitamente, por ejemplo

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

Solucion 2

Renuncie a intentar sobrescribir el flujo de respuesta actual y realice una redirección genuina a una página de error. Esto funciona bien con los seis métodos de prueba del arnés. El siguiente problema fue encontrar una manera de hacer esto que requiriera los mínimos cambios posibles en el proyecto consumidor y abstraiga todos los detalles sucios.

Paso 1: agregue un archivo de marcador de posición ErrorPage.ashx al proyecto que contenga solo una referencia a una clase de manejador de páginas web estándar y sin código subyacente.

 <%@ WebHandler Class="WebApplicationErrorPage" %> 

Paso 2: redirigir a esta página en la clase PageExceptionHandler , usando una clave de sesión para pasar todos los datos requeridos.

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

Paso 3: haga que la clase WebApplicationErrorPage un controlador HTTP realmente tonto que solo escribe en la secuencia de respuesta.

 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(); } } 

Conclusión

Creo que me quedo con la solución 1 porque no implica cortar y cambiar la architecture existente (implementar la solución 2 de verdad será mucho más complejo de lo que sugieren los fragmentos de código anteriores). Archivaré la solución 2 para un posible uso en otro lugar.