route - Manejo de errores en ASP.NET MVC
select asp-for asp-items (8)
El atributo HandleError parece solo procesar excepciones lanzadas por la infraestructura de MVC y no excepciones lanzadas por mi propio código.
Eso es simplemente incorrecto De hecho, HandleError solo "procesará" excepciones lanzadas en su propio código o en código llamado por su propio código. En otras palabras, solo excepciones donde su acción está en la pila de llamadas.
La explicación real del comportamiento que está viendo es la excepción específica que está lanzando. HandleError se comporta de manera diferente con una HttpException. Desde el código fuente:
// If this is not an HTTP 500 (for example, if somebody throws an HTTP 404 from an action method),
// ignore it.
if (new HttpException(null, exception).GetHttpCode() != 500) {
return;
}
¿Cómo puedo manejar correctamente las excepciones lanzadas desde los controladores en ASP.NET MVC? El atributo HandleError
parece solo procesar excepciones lanzadas por la infraestructura de MVC y no excepciones lanzadas por mi propio código.
Usando este web.config
<customErrors mode="On">
<error statusCode="401" redirect="/Errors/Http401" />
</customErrors>
con el siguiente código
namespace MvcApplication1.Controllers
{
[HandleError]
public class HomeController : Controller
{
public ActionResult Index()
{
// Force a 401 exception for testing
throw new HttpException(401, "Unauthorized");
}
}
}
no da como resultado lo que esperaba. En cambio, obtengo la página de error genérica ASP.NET que me indica que modifique mi web.config para ver la información de error real. Sin embargo, si en lugar de lanzar una excepción devuelvo una Vista no válida, obtengo la página /Shared/Views/Error.aspx:
return View("DoesNotExist");
Lanzar excepciones dentro de un controlador como he hecho anteriormente parece pasar por alto toda la funcionalidad de HandleError
, entonces, ¿cuál es la forma correcta de crear páginas de error y cómo juego bien con la infraestructura de MVC?
El módulo de manejo de excepciones amistoso de Jeff Atwood funciona muy bien para MVC. Puede configurarlo completamente en su web.config, sin ningún cambio en el código fuente del proyecto MVC. Sin embargo, necesita una pequeña modificación para devolver el estado HTTP original en lugar de un estado de 200
. Ver esta publicación relacionada en el foro .
Básicamente, en Handler.vb, puede agregar algo como:
'' In the header...
Private _exHttpEx As HttpException = Nothing
'' At the top of Public Sub HandleException(ByVal ex As Exception)...
HttpContext.Current.Response.StatusCode = 500
If TypeOf ex Is HttpException Then
_exHttpEx = CType(ex, HttpException)
HttpContext.Current.Response.StatusCode = _exHttpEx.GetHttpCode()
End If
Elegí el enfoque Controller.OnException (), que para mí es la opción lógica, ya que he elegido ASP.NET MVC, prefiero mantenerme en el nivel de marco y evitar meterme con la mecánica subyacente, si es posible.
Me encontré con el siguiente problema:
Si la excepción se produce dentro de la vista, el resultado parcial de esa vista aparecerá en la pantalla, junto con el mensaje de error.
Lo arreglé borrando la respuesta, antes de configurar filterContext.Result - así:
filterContext.HttpContext.Response.Clear(); // gets rid of any garbage
filterContext.Result = View("ErrorPage", data);
Espero que esto ahorre a alguien más en algún momento :-)
Gracias a kazimanzurrashaid, esto es lo que terminé haciendo en Global.asax.cs:
protected void Application_Error()
{
Exception unhandledException = Server.GetLastError();
HttpException httpException = unhandledException as HttpException;
if (httpException == null)
{
Exception innerException = unhandledException.InnerException;
httpException = innerException as HttpException;
}
if (httpException != null)
{
int httpCode = httpException.GetHttpCode();
switch (httpCode)
{
case (int) HttpStatusCode.Unauthorized:
Response.Redirect("/Http/Error401");
break;
}
}
}
Podré agregar más páginas al HttpContoller en función de cualquier código de error HTTP adicional que necesite.
No creo que pueda mostrar ErrorPage específico basado en el HttpCode con HandleError Attribute y preferiría usar un HttpModule para este propósito. Suponiendo que tengo la carpeta "ErrorPages" donde existe una página diferente para cada error específico y la asignación se especifica en el archivo web.config igual que la aplicación de formulario web normal. Y el siguiente es el código que se usa para mostrar la página de error:
public class ErrorHandler : BaseHttpModule{
public override void OnError(HttpContextBase context)
{
Exception e = context.Server.GetLastError().GetBaseException();
HttpException httpException = e as HttpException;
int statusCode = (int) HttpStatusCode.InternalServerError;
// Skip Page Not Found and Service not unavailable from logging
if (httpException != null)
{
statusCode = httpException.GetHttpCode();
if ((statusCode != (int) HttpStatusCode.NotFound) && (statusCode != (int) HttpStatusCode.ServiceUnavailable))
{
Log.Exception(e);
}
}
string redirectUrl = null;
if (context.IsCustomErrorEnabled)
{
CustomErrorsSection section = IoC.Resolve<IConfigurationManager>().GetSection<CustomErrorsSection>("system.web/customErrors");
if (section != null)
{
redirectUrl = section.DefaultRedirect;
if (httpException != null)
{
if (section.Errors.Count > 0)
{
CustomError item = section.Errors[statusCode.ToString(Constants.CurrentCulture)];
if (item != null)
{
redirectUrl = item.Redirect;
}
}
}
}
}
context.Response.Clear();
context.Response.StatusCode = statusCode;
context.Response.TrySkipIisCustomErrors = true;
context.ClearError();
if (!string.IsNullOrEmpty(redirectUrl))
{
context.Server.Transfer(redirectUrl);
}
}
}
Otra posibilidad (no es cierta en su caso) de que otros que leen esto puedan estar experimentando es que su página de error arroja un error en sí misma o no está implementando:
System.Web.Mvc.ViewPage<System.Web.Mvc.HandleErrorInfo>
Si este es el caso, obtendrá la página de error predeterminada (de lo contrario, obtendría un bucle infinito porque seguiría intentando enviarse a su página de error personalizada). Esto no fue inmediatamente obvio para mí.
Este modelo es el modelo enviado a la página de error. Si su página de error utiliza la misma página maestra que el resto de su sitio y requiere cualquier otra información del modelo, necesitará crear su propio tipo de atributo [HandleError]
o anular OnException
o algo así.
Controller.OnException(ExceptionContext context)
. Anularlo.
protected override void OnException(ExceptionContext filterContext)
{
// Bail if we can''t do anything; app will crash.
if (filterContext == null)
return;
// since we''re handling this, log to elmah
var ex = filterContext.Exception ?? new Exception("No further information exists.");
LogException(ex);
filterContext.ExceptionHandled = true;
var data = new ErrorPresentation
{
ErrorMessage = HttpUtility.HtmlEncode(ex.Message),
TheException = ex,
ShowMessage = !(filterContext.Exception == null),
ShowLink = false
};
filterContext.Result = View("ErrorPage", data);
}
protected override void OnException (ExceptionContext filterContext )
{
if (filterContext != null && filterContext.Exception != null)
{
filterContext.ExceptionHandled = true;
this.View("Error").ViewData["Exception"] = filterContext.Exception.Message;
this.View("Error").ExecuteResult(this.ControllerContext);
}
}