asp.net mvc 3 - Application_Error no se activa cuando customerrors="On"
asp.net-mvc-3 application-error (9)
En caso de uso de ASP.NET MVC5
public class ExceptionPublisherExceptionFilter : IExceptionFilter
{
private static Logger _logger = LogManager.GetCurrentClassLogger();
public void OnException(ExceptionContext exceptionContext)
{
var exception = exceptionContext.Exception;
var request = exceptionContext.HttpContext.Request;
// HttpException httpException = exception as HttpException;
// Log this exception with your logger
_logger.Error(exception.Message);
}
}
Puede encontrarlo en FilterConfig.cs
de la carpeta App_Start
.
Tengo un código en el evento Application_Error
del archivo global.asax
que se ejecuta cuando ocurre un error y me envía por correo electrónico los detalles del error.
void Application_Error(object sender, EventArgs e)
{
var error = Server.GetLastError();
if (error.Message != "Not Found")
{
// Send email here...
}
}
Esto funciona bien cuando lo estoy ejecutando en Visual Studio, sin embargo, cuando publico en nuestro servidor en vivo, el evento Application_Error
no se activa.
Después de algunas pruebas, puedo hacer que Application_Error
se customErrors="Off"
cuando configuro customErrors="Off"
, sin embargo, volver a customErrors="On"
para que el evento no vuelva a customErrors="On"
.
¿Alguien puede sugerir por qué Application_Error
no se customErrors
cuando customErrors
están habilitados en el web.config
?
Encontré un article que describe una forma mucho más limpia de crear páginas de error personalizadas en una aplicación web de MVC3, lo que no impide la posibilidad de registrar las excepciones.
La solución es usar el elemento <httpErrors>
de la sección <system.webServer>
.
Configuré mi Web.config como tal ...
<httpErrors errorMode="DetailedLocalOnly" existingResponse="Replace">
<remove statusCode="404" subStatusCode="-1" />
<remove statusCode="500" subStatusCode="-1" />
<error statusCode="404" path="/Error/NotFound" responseMode="ExecuteURL" />
<error statusCode="500" path="/Error" responseMode="ExecuteURL" />
</httpErrors>
También configuré customErrors
para tener mode="Off"
(como lo sugiere el artículo).
Eso hace que las respuestas sean anuladas por las acciones de un ErrorController. Aquí está ese controlador:
public class ErrorController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult NotFound()
{
return View();
}
}
Las vistas son muy sencillas, solo utilicé la sintaxis estándar de Razor para crear las páginas.
Eso solo debería ser suficiente para poder usar páginas de error personalizadas con MVC.
También necesitaba el registro de Excepciones, así que robé la solución de Mark de usar un ExceptionFilter personalizado ...
public class ExceptionPublisherExceptionFilter : IExceptionFilter
{
public void OnException(ExceptionContext exceptionContext)
{
var exception = exceptionContext.Exception;
var request = exceptionContext.HttpContext.Request;
// log stuff
}
}
Lo último que necesita hacer es registrar el filtro de excepción en su archivo Global.asax.cs :
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new ExceptionPublisherExceptionFilter());
filters.Add(new HandleErrorAttribute());
}
Esto se siente como una solución mucho más limpia que mi respuesta anterior y funciona tan bien como puedo decir. Me gusta especialmente porque no parecía estar luchando contra el framework MVC; ¡esta solución realmente lo aprovecha!
Esta entrada de blog me ayudó:
http://asp-net.vexedlogic.com/2011/04/23/asp-net-maximum-request-length-exceeded/
Si está utilizando IIS 7.0 o una versión superior, puede modificar su archivo Web.config para que maneje las solicitudes que son demasiado grandes. Hay algunas advertencias, pero aquí hay un ejemplo:
<system.webServer>
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="1048576" />
</requestFiltering>
</security>
<httpErrors errorMode="Custom" existingResponse="Replace">
<remove statusCode="404" subStatusCode="13" />
<error statusCode="404" subStatusCode="13" prefixLanguageFilePath="" path="/UploadTooLarge.htm" responseMode="Redirect" />
</httpErrors>
</system.webServer>
Aquí hay detalles adicionales sobre estos elementos de archivo de configuración:
http://www.iis.net/ConfigReference/system.webServer/security/requestFiltering/requestLimits
El código de estado 404.13 se define como "Longitud de contenido demasiado grande". Una cosa importante a tener en cuenta es que maxAllowedContentLength
se especifica en bytes. Esto es diferente de la configuración de maxRequestLength
que se encuentra en la sección <system.web>
, que se especifica en kilobytes.
<system.web>
<httpRuntime maxRequestLength="10240" />
</system.web>
También tenga en cuenta que el atributo de path
debe ser una ruta absoluta cuando el Redirect
responseMode
es Redirect
, de modo que anteponga el nombre del directorio virtual, si corresponde. Las respuestas informativas de Jesse Webb muestran cómo hacer esto con responseMode="ExecuteURL"
, y creo que ese enfoque también funcionaría bien.
Este enfoque no funciona si está desarrollando usando el Visual Studio Development Server (Cassini, el servidor web integrado en Visual Studio). Supongo que funcionaría en IIS Express, pero no lo he probado.
Estaba teniendo el mismo problema donde Application_Error()
no recibía ningún golpe. Intenté todo, hasta que finalmente di un paso adelante en lo que estaba sucediendo. ¡Tenía un código personalizado en un evento ELMAH que estaba agregando JSON al correo electrónico que envía, y había un error nulo allí!
La reparación del error interno permitió que el código continuara en el evento Application_Error()
como se esperaba.
Lo resolví creando un ExceptionFilter y registrando el error allí en lugar de Application_Error. Todo lo que necesita hacer es agregar una llamada a la función RegisterGlobalFilters
log4netExceptionFilter.cs
using System
using System.Web.Mvc;
public class log4netExceptionFilter : IExceptionFilter
{
public void OnException(ExceptionContext context)
{
Exception ex = context.Exception;
if (!(ex is HttpException)) //ignore "file not found"
{
//Log error here
}
}
}
Global.asax.cs
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new log4netExceptionFilter()); //must be before HandleErrorAttribute
filters.Add(new HandleErrorAttribute());
}
Me gusta la respuesta de Mark con ExceptionFilter, pero otra opción, si tiene todos sus controladores derivados del mismo controlador base, es simplemente anular OnException en su controlador base. Puede hacer su registro y correo electrónico allí. Esto tiene la ventaja de poder usar las dependencias que ya hayas inyectado en tu controlador base con tu contenedor IoC.
Todavía puede usar su IoC con un IExceptionFilter, pero es un poco más complicado configurar sus enlaces.
Para evitar esto, terminé dejando los clientes desactivados y manejando todos los errores del evento Application_Error en global.asax. Es un poco complicado con MVC ya que no quería devolver un redireccionamiento 301, quería devolver los códigos de error adecuados. Se pueden ver más detalles en mi blog en http://www.wduffy.co.uk/blog/using-application_error-in-asp-net-mvcs-global-asax-to-handle-errors/ pero el código final es listado a continuación ...
void Application_Error(object sender, EventArgs e)
{
var error = Server.GetLastError();
var code = (error is HttpException) ? (error as HttpException).GetHttpCode() : 500;
if (code != 404)
{
// Generate email with error details and send to administrator
}
Response.Clear();
Server.ClearError();
string path = Request.Path;
Context.RewritePath(string.Format("~/Errors/Http{0}", code), false);
IHttpHandler httpHandler = new MvcHttpHandler();
httpHandler.ProcessRequest(Context);
Context.RewritePath(path, false);
}
Y aquí está el controlador
public class ErrorsController : Controller
{
[HttpGet]
public ActionResult Http404(string source)
{
Response.StatusCode = 404;
return View();
}
[HttpGet]
public ActionResult Http500(string source)
{
Response.StatusCode = 500;
return View();
}
}
Por lo que yo sé, está pasando el control a la página especificada en el parámetro url y su notificación de evento se ubicaría aquí, en lugar de Application_Error
<customErrors defaultRedirect="myErrorPage.aspx"
mode="On">
</customErrors>
Se puede encontrar mucha información aquí: http://support.microsoft.com/kb/306355
ACTUALIZAR
Como esta respuesta proporciona una solución, no la editaré, pero he encontrado una forma mucho más limpia de resolver este problema. Ver mi otra respuesta para más detalles ...
Respuesta Original:
Descubrí por qué el método Application_Error()
no se está invocando ...
Global.asax.cs
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute()); // this line is the culprit
}
...
}
De forma predeterminada (cuando se genera un nuevo proyecto), una aplicación MVC tiene cierta lógica en el archivo Global.asax.cs
. Esta lógica se usa para mapear rutas y registrar filtros. Por defecto, solo registra un filtro: un filtro HandleErrorAttribute
. Cuando customErrors están activados (o mediante solicitudes remotas cuando está configurado en RemoteOnly), HandleErrorAttribute le dice a MVC que busque una vista de Error y nunca llama al método Application_Error()
. No pude encontrar la documentación de esto, pero se explica en esta respuesta en programmers.stackexchange.com .
Para obtener el método ApplicationError () llamado para cada excepción no controlada, simplemente elimine la línea que registra el filtro HandleErrorAttribute.
Ahora el problema es: cómo configurar los customErrors para obtener lo que quieres ...
La sección customErrors está predeterminada en redirectMode="ResponseRedirect"
. Puede especificar que el atributo defaultRedirect también sea una ruta MVC. Creé un ErrorController que era muy simple y cambié mi web.config para que se vea así ...
web.config
<customErrors mode="RemoteOnly" redirectMode="ResponseRedirect" defaultRedirect="~/Error">
<error statusCode="404" redirect="~/Error/PageNotFound" />
</customErrors>
El problema con esta solución es que hace una redirección 302 a las URL de error y luego esas páginas responden con un código de estado de 200. Esto lleva a que Google indexe las páginas de error, lo cual es malo. Tampoco es muy conforme con las especificaciones HTTP. Lo que quería hacer no era redirigir y sobrescribir la respuesta original con mis vistas de error personalizadas.
Traté de cambiar redirectMode="ResponseRewrite"
. Desafortunadamente, esta opción no admite rutas MVC , solo páginas HTML estáticas o ASPX. Traté de usar una página HTML estática al principio, pero el código de respuesta todavía era 200 pero, al menos no redirigió. Luego obtuve una idea de esta respuesta ...
Decidí darme por vencido en MVC para el manejo de errores. Error.aspx
un Error.aspx
y un PageNotFound.aspx
. Estas páginas eran muy simples pero tenían una pieza de magia ...
<script type="text/C#" runat="server">
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
Response.StatusCode = (int) System.Net.HttpStatusCode.InternalServerError;
}
</script>
Este bloque le dice a la página que se le envíe el código de estado correcto. En general, en la página PageNotFound.aspx, utilicé HttpStatusCode.NotFound
. Cambié mi web.config para que se vea así ...
<customErrors mode="RemoteOnly" redirectMode="ResponseRewrite" defaultRedirect="~/Error.aspx">
<error statusCode="404" redirect="~/PageNotFound.aspx" />
</customErrors>
¡Todo funcionó perfectamente!
Resumen:
- Elimine la línea:
filters.Add(new HandleErrorAttribute());
- Use el método
Application_Error()
para registrar excepciones - Use customErrors con ResponseRewrite, apuntando a páginas ASPX
- Haga que las páginas ASPX sean responsables de sus propios códigos de estado de respuesta
Hay algunas desventajas que he notado con esta solución.
- Las páginas ASPX no pueden compartir ningún marcado con las plantillas Razor, tuve que reescribir el encabezado estándar y el pie de página de mi sitio web para obtener una apariencia coherente.
- Se puede acceder directamente a las páginas * .aspx al presionar sus URL
Hay soluciones para estos problemas, pero no me preocupó lo suficiente como para hacer ningún trabajo adicional.
¡Espero que esto ayude a todos!