miércoles, 22 de septiembre de 2010

Gestión de errores en ASP.NET

Hola, este post es tanto de mi compañero de trabajo Antonio como mío. La realidad es que sólo queremos controlar los errores de ASP.NET ¿Es pedir tanto? ¿Es demasiado osado? Yo creo que no, pero para saber cómo manejarlos primero hay que saber cómo funcionan. Ese es el propósito de este post, entenderlos y después ya controlarlos.

CustomErrors

http://msdn.microsoft.com/es-es/library/h0hfz6fc(VS.80).aspx

mode = [On|Off|RemoteOnly], determina si los errores personalizados está o no activos, o si sólo ven para clientes remotos.

defaultRedirect = url (una página .aspx, .htm, etc., con una dirección absoluta, relativa al fichero web.config o relativa al proyecto)

A la dirección url especificada en defaultRedirect, la única información disponible del error es el parámetro aspxerrorpath que indica que página generó el error:

?aspxerrorpath=/ControlErrores/Default.aspx

Además, en la página defaultRedirect no está nunca disponible Server.GetLastError.

También se puede especificar páginas de errores personalizadas atendiendo a los códigos de estado Http que devuelve el servidor, por ejemplo, el siguiente código hace lo siguiente:

Todos los errores no controlados van a ser redirigidos a la página PaginaError.aspx, y en concreto los errores con código 404 (fichero no encontrado), van a ir a PaginaNoEncontrada.aspx .

    <customErrors mode="On" defaultRedirect="~/PaginaError.aspx">

      <error statusCode="404" redirect="~/PaginaNoEncontrada.aspx"/>

    </customErrors>

Recuerda: customErrors sólo gestiona errores a nivel de petición de página, no para recursos que solicita la página, esto es que una imagen que carga la página y que no se encuentra (404) no provocará que veamos el error personalizado por customErrors.

CustomErrors presenta las siguientes ventajas y desventajas:

  • Ventajas:
    • Configuración sencilla a través del web.config.
    • Páginas de error sencillas, incluso ficheros .htm, lo que facilita el diseño de las mismas por terceros, por ejemplo un diseñador web.
    • Alto grado de personalización de páginas en función de códigos de estado Http.
  • Desventajas:
    • No hay información disponible sobre el error sucedido, luego tareas de log y traza de errores no son válidas a través de las páginas de error personalizadas.

Otra cosa a tener en cuenta es como funciona CustomErrors en lo relativo a la entrega de la página personalizada de error en el cliente. Hay muy buen blog al respecto en http://geeks.ms/blogs/jalarcon/archive/2010/11/07/modo-de-redirecci-243-n-de-errores-personalizados-en-asp-net-y-su-impacto-en-seo-e-imagen.aspx


Page_Error

A través de este evento de la página, controlamos errores sucedidos en la misma página, tenemos disponible Server.GetLastError() y normalmente también deberíamos limpiar el error con Server.ClearError() del servidor para evitar que el error siga propagándose y por ejemplo entre en juego los errores personalizados de customErrors.

    Protected Sub Page_Error(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Error

        Dim ex As Exception = Server.GetLastError

        ' TODO do something...       

        ' La siguiente instrucción impide que la excepción se propague más allá de este método

        Server.ClearError()

    End Sub

Recuerda: Al igual que customErrors, Page_Error sólo gestiona errores a nivel de petición de página, no de recursos de la página.

Page_Error presenta las siguientes ventajas y desventajas:

  • Ventajas:
    • Está disponible la información de error a través de Server.GetLastError().
    • Posibilidad de detener o proseguir la propagación de manejo del error a través de Server.ClearError().
    • Compatible con customErrors (en caso de no llamar a Server.ClearError()).
  • Desventajas:
    • Cada página debería implementar su propio método Page_Error.

Application_Error

Finalmente, es posible gestionar las excepciones de forma global al proyecto a través del evento Application_Error del fichero global.asax.

    Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)

        Dim ex As Exception = Server.GetLastError.GetBaseException

        ' TODO do something...

        Server.ClearError()       

    End Sub

Application_Error presenta las siguientes ventajas y desventajas:

  • Ventajas
    • Está disponible la información de error a través de Server.GetLastError().
    • Compatible con Page_Error (si no se llama a Server.ClearError()).
    • Compatible con customErrors (si no se llama a Server.ClearError()).
    • Gestión unificada de errores en toda la aplicación.
  • Desventajas
    • Los errores de servicios web no pasan por este evento.

Como vemos el flujo de un error en el siguiente:

  • Si hay disponible un Page_Error se procesa.
    • Si se llama a Server.ClearError el error se detiene.
  • Si hay disponible Application_Error se procesa.
    • Si se llama a Server.ClearError el error se detiene
  • Si ha disponible una página de error personalizada a través de customErrors se muestra y el error se detiene.
  • Se muestra una página de error genérica.

Recuerda: Siempre que se quiere obtener la excepción a través del método Server.GetLastError(), asegúrate de llamar al método GetBaseException() para obtener la excepción original.

Dim ex As Exception = Server.GetLastError.GetBaseException

A diferencia de customErrors y Page_Error, en Application_Error podremos controlar absolutamente todos los errores sucedidos en la aplicación, esto significa que por ejemplo una solicitud de un recurso de imagen que no existe por parte de una página, también será interceptado por Application_Error.

PRB: ThreadAbortException Occurs If You Use Response.End, Response.Redirect, or Server.Transfer

http://support.microsoft.com/kb/312629/EN-US/

El error ThreadAbortException parece que sólo es interceptable dentro de un Try..Cath. Esto es que no se propaga a Page_Error, Application_Error o customErrors.

ASP.NET AJAX

Si está activado customErrors en nuestro fichero web.config y AllowCustomErrorsRedirect=”True” para nuestro control ScriptManager, Cuando el motor de renderización parcial de página AJAX produzca un error, es capaz de llevar a cabo una redirección de cliente con el siguiente código que devuelve al navegador:

HTTP/1.1 200 OK

75|pageRedirect||/ControlErrores/PaginaError.aspx?aspxerrorpath=/ControlErrores/default.aspx|

En caso de no estar activo cualquier de los anteriores valroes, lo que devuelve AJAX no es un redirección sino la excepción no controlada:

27|error|500|panicoenlaxbox.blogspot.com|

Que mostrara alguno de los siguientes mensajes (depende de si estamos depurando o no).

clip_image002

image

Para evitar este mensaje tenemos 2 posibles momentos donde interceptarlo. Primero de la forma más sencilla que es estableciendo una valor para la propiedad AsynPostBackErrorMessage del control ScriptManager desde el marcado o bien manejando el evento AsyncPostBackError desde el servidor, pero Ojo!!, porque esto no impide que aparezca igualmente una alerta en el navegador, sólo nos ayuda a “esconder” el error original y devolver nuestro propio mensaje de error. Por ejemplo si AsyncPostBackErrorMessage = “MI ERROR”, entonces…

image

Como es lógico, esto está bien pero no soluciona la alerta (y posible depuración, porque recordar que esto no es una alerta sino una “excepción de javascript”).

Para evitar cualquier alerta o excepción javascript y controlar un error de ASP.NET AJAX desde cliente (no confundir con servidor y ver que este control de errores que ponemos a continuación pasa exclusivamente en cliente y después de que el error ya ha pasado por tu ciclo de vida, esto es Page_Error, Application_Error, etc.):

            function pageLoad(sender, e) {

                var prm = Sys.WebForms.PageRequestManager.getInstance();

                prm.add_endRequest(function (sender, e) {

                    if (e.get_error()) {

                        alert(e.get_error().message); //Alerta personalizada.

                        e.set_errorHandled(true); //Evitar error predeterminado.

                    }

                });

            }

Si hablamos de servicios web, cabe mencionar lo siguiente:

  • El error no ha pasado por Application_Error.
  • customErrors no se tiene en cuenta.

Si no hay ningún control extra, un servicio web que falla devolverá una alerta predeterminada de javascript como la siguiente:

clip_image004

Una solicitud de error a un servicio web devolverá lo siguiente:

HTTP/1.1 500 Internal Server Error

{"Message":"panicoenlaxbox","StackTrace":"   en Servicio1.HelloWorld() en C:\\Users\\sleon\\Documents\\Visual Studio 2010\\WebSites\\ControlErrores\\App_Code\\Servicio1.vb:línea 15","ExceptionType":"System.Exception"}

Para controlar el error del servicio web desde cliente (exclusivamente javascript) hay que hacer lo siguiente:

            Servicio1.HelloWorld(exito, fracaso);

            function exito(result, context) {

                alert("éxito");

            }

            function fracaso(result, context) {

                alert(result.get_message());

            }


Conclusiones

Seguro que hay muchas formas y mejores de controlar las excepciones en ASP.NET, pero tras estudiar todas las posibilidades, yo personalmente recomiendo el siguiente escenario:

Todo código susceptible de lanzar una excepción debería ir acompañado de su propia gestión con Try..Cath. ¡¡Por favor, que el error no llegue nunca a Page_Error ni a Application_Error, debería estar controlado siempre!!.

Si una página es propensa a errores no controlados (que la verdad es que dicho así suena mal y no se me ocurre ningún ejemplo válido), se podría utilizar puntualmente Page_Error, pero debería ser decisión del programador en cada página si utilizar esta rutina o no, yo creo que tampoco abusaré de la misma.

Siempre y sin excepción, se programará el evento Application_Error en el fichero global.asax. El único propósito de esta rutina será registrar el error en nuestro repositorio elegido (base de datos, log del sistema, etc.). Insisto, no hará nada más, solo registrar.

Siempre y sin excepción se utilizará customErrors al menos con una página de error (defaultRedirect) y si nos vemos con ganas y creativos, pues el 404 es buen candidato a tener su propia página de error, pero esto ya lo decide el programador.

Además customErrors parece una buena idea que esté activo con el modo RemoteOnly, para tener una vía de escape si el registro de Application_Error no nos convence ni nos ayuda a ver que pasó.

Un saludo!

3 comentarios: