Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

17 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
domingo, 14 de junio de 2009

Sorpresa El control de errores en aplicaciones web es fundamental si queremos ofrecer un interfaz robusto y amigable para los usuarios en cualquier situación. No hay nada más frustrante para un usuario que una pantalla de error con contenidos indescifrables y que no le aportan alternativas de salida.

El framework ASP.NET MVC nos ofrece mecanismos de control de errores muy potentes basada en la utilización del atributo HandleError, el cual definirá la vista que será mostrada al usuario cuando se produzca alguna excepción no controlada en el código de los controladores, siempre que en el web.config se haya activado el uso de errores personalizados mediante la propiedad CustomErrors.

En este post vamos a profundizar en el uso del atributo HandleError, comentando cómo se implementa en el controlador, su ámbito de actuación, los parámetros que ofrece y la forma de crear las vistas para mostrar los errores de forma amigable.

El controlador

HandleError puede ser declarado tanto a nivel de clase (controlador) como a nivel de acción. En el primer caso, se establecerá el comportamiento general para todas las acciones del controlador, mientras que en el segundo será aplicable sólo a la acción a la que se asocie el atributo:

// Control de errores a nivel de clase de controlador, 
// que será aplicado a todas las acciones del mismo.
[HandleError()]
public class HomeController : Controller
{
...
}


// Control de errores a nivel de acción concreta...
[HandleError(ExceptionType=typeof(ArgumentException), View="ArgumentError")]
public ActionResult Calculate(int a, int b)
{
ViewData[
"results"] = calculateSomething();
return View();
}

En realidad, el comportamiento definido en el atributo HandleError a nivel de clase también se aplicará a los errores generados por las vistas u otros resultados (ActionResult) retornados por los controladores. Es decir, sobre el segundo de los ejemplos anteriores, la vista “ArgumentError” (que existirá en un archivo llamado ArgumentError.aspx) será invocada cuando la excepción ArgumentException sea lanzada bien por el propio controlador, o bien por la vista “Calculate” que retorna por defecto.

Un último detalle sobre esto: el atributo HandleError puede ser especificado tantas veces como necesitemos sobre la misma acción o controlador, indicando comportamientos para distintos tipos de excepción. El atributo que se tendrá en cuenta cuando se produzca un error será el primero que se encuentre cuyo tipo de excepción (parámetro ExceptionType) sea compatible con la excepción lanzada.

El manejador de errores que se empleará en una acción será el primero que corresponda al tipo de excepción producida, teniendo en cuenta tanto los atributos que adornan la acción como los que acompañan a su controlador, y siempre según un orden preestablecido.

Veamos con más detenimiento los parámetros que admite la declaración del atributo.


Parámetros de HandleError


        [HandleError(
ExceptionType
=typeof(DivisionByZero),
View
="ErrorPersonalizado",
Master
="MaestraErrores",
Order
= 0)
]
public ActionResult Index()
...

  • ExceptionType permite indicar el tipo de excepción que se pretende controlar. Por defecto, el sistema entenderá que la regla se refiere al tipo base Exception, por lo que se aplicará a todos los errores que se generen, pues todas las excepciones heredan de esta clase.
  • View, el nombre de la vista que será mostrada al usuario. Por defecto se tomará el valor “Error”, por eso en la plantilla de proyectos ASP.NET MVC ya existe una vista con este nombre.
  • Master, la página maestra con la que será renderizada la vista, independientemente de lo que tenga declarado ésta.
  • Order, un valor numérico (por defecto –1, el más prioritario) que indica la prioridad de aplicación de esta regla cuando el sistema encuentre varios atributos HandleError aplicables al mismo elemento y que puedan presentar conflictos. Los valores más pequeños, Por ejemplo, si no se indicara este parámetro en el siguiente caso, el resultado dependería del orden de declaración, lo cual no es demasiado recomendable:
    [HandleError(Order=10, View="ErrorGenerico")]
    public class HomeController : Controller
    {
    ...
    [HandleError (ExceptionType
    =typeof(DivisionByZero),
    View
    ="OperacionIncorrecta", Order=1)]
    public ActionResult Calculate()
    ...

    Como puede intuirse, esto hará que en caso de producirse una división por cero, se muestre la vista “OperacionIncorrecta” y no la “ErrorGenerico”.

Cada vez que utilicemos HandleError es conveniente tener muy en cuenta la prioridad (definida en la propiedad Order), el alcance (las excepciones a tratar, definidas en la propiedad ExceptionType), así como los valores por defecto en cada caso. Esto evitará comportamientos misteriosos del sistema una vez se produzcan errores en tiempo de ejecución.

Acceso desde la vista a la información del error

Ya hemos comentado anteriormente que la vista que será mostrada a los usuarios cuando se produzca un error será la indicada en el parámetro View del atributo HandleError, o la vista "Error", si este parámetro no es informado. Sea cual sea, el archivo nombredevista.aspx deberá estar localizable por el motor de vistas en el momento de su lanzamiento (por cierto, si no te gustan las ubicaciones por defecto, puedes ver cómo modificar la forma en la que se buscan las vistas en este post).

La vista de un error es una página .aspx normal, como una vista más de la web, pero con la particularidad de que puede recibir información sobre el error que ha provocado su presentación. De hecho, se trata de una vista tipada que hereda de ViewPage<System.Web.Mvc.HandleErrorInfo>, de forma que  la propiedad Model será del tipo HandleErrorInfo, clase que nos ofrece completa información sobre el origen del problema:

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" 
Inherits
="System.Web.Mvc.ViewPage<System.Web.Mvc.HandleErrorInfo>" %>

<asp:Content ID="errorTitle" ContentPlaceHolderID="TitleContent" runat="server">
Error en el sistema
</asp:Content>
<asp:Content ID="errorContent" ContentPlaceHolderID="MainContent" runat="server">
<h2>
Ups, se ha producido un ligero inconveniente...
</h2>
<p>
La acción
<%= Model.ActionName %>
del controlador
<%= Model.ControllerName %>
ha lanzado la excepción
<%= Model.Exception.GetType().Name %> con
el mensaje "
<%= Model.Exception.Message %>".
</p>
</asp:Content>

A pesar de no ser un buen ejemplo como pantalla del error amigable para el usuario ;-), el código anterior ilustra cómo es posible acceder desde la vista a los datos del contexto del error proporcionados por el entorno, y cómo la clase HandleErrorInfo nos ofrece una información sobre la acción en la que se ha lanzado la excepción, el controlador en el que se encuentra la misma, y lo más interesante, nos ofrece en la propiedad Exception la excepción producida, por lo que tendremos acceso a su tipo, descripción e incluso al estado de la pila de llamadas en el momento de producirse el error.



Publicado en: www.variablenotfound.com

4 Comentarios:

Gabriel Porras dijo...

Hablando de errores conoces a elmah (http://code.google.com/p/elmah/).

Acabo de leer que también funciona con Asp .Net MVC.

Saludos

josé M. Aguilar dijo...

Hola, Gabriel!

Pues no, no conocía Elmah, y la verdad es que tiene una pinta excelente. Me lo apunto para echarle un vistazo en profundidad en cuanto tenga un ratillo.

Y efectivamente, en la wiki describen cómo utilizarlo con NET MVC.

Un saludo y gracias por el aporte.

Unknown dijo...

Estimado José.

Con esta ya van como 4 preguntas que te hago y pos la verdad me gusta bastante tus publicaciones, ahora tengo una pregunta que me parece muy interesante, mejor primero te explico mi idea:

Crear un controlador padre del que todos los controladores heredarán el atributo [HandleError(View = "ErrorGenericopersonalizado")], entonces lógicamente todas mis acciones de todos mis controladores tendrían este atributo y ante cualquier excepción no controlada se redireccionaria a la vista "ErrorGenericopersonalizado", si entendi bien asi es como funcionaría y quedaría todo chevere (todo OK) jeje, pero si quiere procesar el exception? (por ejemplo loguearlo), como podria hacerlo? Revisando tu ejemplo pienso que tal vez desde la vista genérica del error podría llamar a una action pasandole el modelo de la exception y en esta action procesar esta exeption, solo es una idea no se si sera posible, pero a ver si podrías ayudarme un poco al respecto. Te agradezco de antemano.

Saludos cordiales.

José María Aguilar dijo...

Hola!

Gracias por tus comentarios :)

Respecto a tu consulta, no, mejor no llamar a un action desde una vista sólo para eso.

Sería más razonable crear un atributo personalizado, quizás heredando de HandleError, e introducir en él la lógica para registrar el error usando el logger que consideres oportuno.

Otra posibilidad es usar Elmah (http://elmah.github.io/), que ya te lo da hecho :)

Saludos!