Control de excepciones con ASP.NET MVC

Generalmente, el hecho de no controlar las excepciones de una aplicación, y mostrar una página como la siguiente al usuario, supone una pérdida de calidad palpable en el producto.

En ASP.NET MVC podemos controlar estos mensajes redirigiendo al usuario a unas páginas más amigables y mostrando un mensaje menos alarmante de la aplicación. Si lo único que necesitamos es controlar excepciones personalizadas o aquellas que no pertenezcan a la rama de HttpException podríamos realizar los siguientes pasos:

  1. Habilitamos customErrors en el archivo web.config de la aplicación.
    <customErrors mode="On"/>

  2. Utilizamos el action filter HandleError, ofrecido por ASP.NET MVC, en el controlador, el cual redirige todas las excepciones a la página Error.aspx por defecto ubicada en Views/Shared, aunque podemos indicarle otra vista en el atributo.
  3. using System;
    using System.Web.Mvc;

    namespace HandleErrorASPNETMVC2RC2.Controllers
    {
    [HandleError(Order = 2)]
    public class HomeController : BaseController
    {
    public ActionResult Index()
    {
    ViewData["Message"] = "Welcome to ASP.NET MVC!";

    return View();
    }

    [HandleError(ExceptionType = typeof(DivideByZeroException), View = "DivideByZeroError", Order = 1)]
    public ActionResult DivideByZeroExceptionAction()
    {
    throw new DivideByZeroException("DivideByZeroException is handled!");
    }

    }
    }

Si quisiéramos controlar excepciones de tipo HttpException como la siguiente:

public ActionResult NotFoundHttpExcepcion()
{
throw new HttpException(404, "Not Found duh!");
}

No sería posible solamente con el action filter proporcionado.

Tenemos varias alternativas para manejar estas excepciones. En este post, voy a exponer las dos que me han resultado más factibles:

HACIENDO USO DE APPLICATION_ERROR (GLOBAL.ASAX)

Podríamos definir un comportamiento para los errores de la aplicación, a través de Application_Error, de la siguiente manera (obtenido a través de stackoverflow):

protected void Application_Error(object sender, EventArgs e)
{
var exception = Server.GetLastError();

Response.Clear();

var httpException = exception as HttpException;

var routeData = new RouteData();
routeData.Values.Add("controller", "HttpError");

if (httpException == null)
routeData.Values.Add("action", "Index");
else
{
switch (httpException.GetHttpCode())
{
case 404:
routeData.Values.Add("action", "NotFound");
break;
case 403:
routeData.Values.Add("action", "Forbidden");
break;
default:
routeData.Values.Add("action", "Default");
break;
}
}

routeData.Values.Add("error", exception);

Server.ClearError();

IController errorController = new HttpErrorController();
errorController.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));

}

En este caso tenemos un controlador llamado HttpError con una serie de vistas: Index, NotFound, Forbidden y Default. La parte positiva de esta forma es que, además de tener la posibilidad de redirigir a una vista personalizada, podemos realizar alguna acción, a través del controlador, antes de finalizar la petición. Además podemos utilizar este método de forma conjunta con HandleError para cubrir todas las excepciones posibles.

SOBREESCRIBIENDO ONEXCEPTION

Por otro lado, si lo que queremos es utilizar un atributo común para todas las excepciones, incluyendo HttpExceptions, podemos sobrescribir el action filter HandleError ampliando su funcionalidad de la siguiente manera:

using System.Web;
using System.Web.Mvc;

namespace HandleErrorASPNETMVC2RC2
{
public class HandleErrorHttpException : HandleErrorAttribute
{

public override void OnException(ExceptionContext filterContext)
{
if (!filterContext.IsChildAction && (!filterContext.ExceptionHandled && filterContext.HttpContext.IsCustomErrorEnabled))
{
var httpException = filterContext.Exception as HttpException;
if (httpException != null)
{
var controllerName = (string)filterContext.RouteData.Values["controller"];
var actionName = (string)filterContext.RouteData.Values["action"];
var model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);

var viewResult = new ViewResult
{
ViewName = View,
MasterName = Master,
ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
TempData = filterContext.Controller.TempData
};
filterContext.Result = viewResult;
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusCode = httpException.GetHttpCode();
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true; //Deshabilita el manejo de errores de IIS
}
else
base.OnException(filterContext);
}
}
}
}

De esta manera, todas aquellas excepciones que pertenezca a HttpException pasarán por el primer bloque y  para cualquier otro tipo de excepción utilizará el método de la clase base.

Implementando esta segunda opción, podemos utilizar el nuevo action filter para controlar los errores de nuestros controladores.

using System;
using System.Web;
using System.Web.Mvc;

namespace HandleErrorASPNETMVC2RC2.Controllers
{
[HandleErrorHttpException(Order = 2)]
public class HomeController : BaseController
{
public ActionResult Index()
{
ViewData["Message"] = "Welcome to ASP.NET MVC!";

return View();
}

[HandleErrorHttpException(ExceptionType = typeof(DivideByZeroException), View = "DivideByZeroError", Order = 1)]
public ActionResult DivideByZeroExceptionAction()
{
throw new DivideByZeroException("DivideByZeroException is handled!");
}

public ActionResult Fordibben()
{
throw new HttpException(403, "Forbidden duh!");
}

[HandleErrorHttpException(ExceptionType = typeof(HttpException), View = "NotFound", Order = 1)]
public ActionResult NotFoundHttpExcepcion()
{
throw new HttpException(404, "Not Found duh!");
}

[HandleErrorHttpException(ExceptionType = typeof(InvalidOperationException), View = "InvalidOperationException", Order = 1)]
public ActionResult ViewDoesntExist()
{
return View("Doesnt Exist");
}
}
}

Nota: Cuando intentamos realizar un retorno a una vista que no existe el tipo de excepción es InvalidOperationException como se muestra en el último caso.

Espero que haya sido de utilidad.

¡Saludos!

6 comentarios en “Control de excepciones con ASP.NET MVC”

  1. Hola Roberto,

    La implementación funciona de la misma manera, aunque realices la llamada a través de AJAX, lo que pasa es que la respuesta se debería manipular de otra forma.

    Cuando utilizas el helper de Ajax y añades las propiedades al objeto AjaxOptions, tienes una propiedad llamada OnFailure donde puedes especificar qué función js quieres que se ejecute en el caso de error.

    <%=Ajax.ActionLink("HttpException 404 with Ajax", "NotFoundHttpExcepcion", new AjaxOptions { OnFailure = "onException", OnSuccess = "onSuccess", UpdateTargetId = "ajaxTest" })%>

    Realmente el control de errores sería a través de esa función.

    Puedes recuperar el mensaje del error:

    <script language="javascript" type="text/javascript">
    function onException(d) {
    alert(d.get_response().get_statusText());
    }
    </script>

    O bien puedes recuperar el mensaje de respuesta (obtenemos la página a donde nos hubiera redirigido con el control de errores que implementamos).

    <script language="javascript" type="text/javascript">
    function onException(d) {
    $("#ajaxTest").html(d.get_response()._xmlHttpRequest.responseText);
    }
    </script>

    Espero que resuelva tu duda.

    ¡Saludos!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *