Application_Error no se dispara cuando customerrors = "On"


124

Tengo un código en el evento global.asaxdel archivo Application_Errorque 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 Application_Errorevento no se dispara.

Después de algunas pruebas, puedo Application_Errordisparar cuando configuro customErrors="Off", sin embargo, volver a configurarlo para que customErrors="On"el evento no vuelva a dispararse.

¿Alguien puede sugerir por Application_Errorqué no se dispararía cuando customErrorsestán habilitados en el web.config?


Tengo exactamente el mismo problema. También encontré esta pregunta SO: stackoverflow.com/questions/3713939/… que sugiere poner el servidor IIS7 en modo clásico. Lamentablemente, esa no es una opción para nosotros. ¿Alguien tiene alguna solución mejor?
Jesse Webb

Aquí hay otra pregunta relacionada y sus respuestas (ninguna de las cuales son aceptadas) sugieren no usar Application_Error () en absoluto ... stackoverflow.com/questions/1194578/…
Jesse Webb

@Gweebz He publicado una respuesta sobre cómo resolví esto, pero todavía no encontré ninguna documentación sólida sobre por qué estaba obteniendo este comportamiento.
WDuffy

Agregué una respuesta que explica por qué Application_Error()no se invoca el método. También expliqué mi solución final.
Jesse Webb

Realmente recomiendo este artículo para que funcionen los errores personalizados.
ThomasArdal

Respuestas:


133

ACTUALIZACIÓN
Dado que esta respuesta proporciona una solución, no la editaré, pero he encontrado una forma mucho más limpia de resolver este problema. Vea mi otra respuesta para más detalles ...

Respuesta original:
descubrí por qué Application_Error()no se invoca el método ...

Global.asax.cs

public class MvcApplication : System.Web.HttpApplication
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute()); // this line is the culprit
    }
...
}

Por defecto (cuando se genera un nuevo proyecto), una aplicación MVC tiene cierta lógica en el Global.asax.csarchivo. Esta lógica se utiliza para mapear rutas y registrar filtros. Por defecto, solo registra un filtro: un HandleErrorAttributefiltro. Cuando customErrors están activados (o mediante solicitudes remotas cuando se establece en RemoteOnly), HandleErrorAttribute le dice a MVC que busque una vista de Error y nunca llama al Application_Error()método. No pude encontrar documentación de esto, pero se explica en esta respuesta en programmers.stackexchange.com .

Para obtener el método ApplicationError () para cada excepción no controlada, simplemente elimine la línea que registra el filtro HandleErrorAttribute.

Ahora el problema es: cómo configurar CustomErrors para obtener lo que desea ...

La sección customErrors está predeterminada en redirectMode="ResponseRedirect". También puede especificar que el atributo defaultRedirect sea una ruta MVC. Creé un ErrorController que era muy simple y cambié mi web.config para que se viera 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 sus URL de error y luego esas páginas responden con un código de estado 200. Esto lleva a que Google indexe las páginas de error, lo cual es malo. Tampoco es muy conforme a la especificación 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. Al principio intenté usar una página HTML estática, pero el código de respuesta seguía siendo 200 pero, al menos, no se redirigió. Entonces tuve una idea de esta respuesta ...

Decidí renunciar a MVC para el manejo de errores. Creé un Error.aspxy a 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 sirva con el código de estado correcto. En general, en la página PageNotFound.aspx, usé en su HttpStatusCode.NotFoundlugar. 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ó a la perfección!

Resumen:

  • Eliminar la línea: filters.Add(new HandleErrorAttribute());
  • Use el Application_Error()método 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 un par de inconvenientes 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 marcado estándar de encabezado y pie de página de nuestro sitio web para una apariencia consistente.
  • Se puede acceder directamente a las páginas * .aspx presionando sus URL

Hay soluciones para estos problemas, pero no me preocuparon lo suficiente como para hacer un trabajo extra.

¡Espero que esto ayude a todos!


2
+1! Muy buena solución pero, ¿cuáles podrían ser las consecuencias de mezclar MVC con páginas aspx?
Diego

2
Hemos tenido esto en PROD durante un par de meses y no he encontrado un impacto negativo. El único 'problema' fue que tuvimos que cambiar nuestro CI e implementarlo para hacer una tarea AspCompile MSBUILD porque MVC no lo necesita, pero cuando agregamos los archivos .as , lo requirieron. Puede haber otros problemas, pero aún no han aparecido ...
Jesse Webb

He estado tratando de usar este enfoque en MVC4, y aparentemente solo funciona para mí cuando lo tengo <customErrors mode="Off" />. Haber filters.Add(new HandleErrorAttribute());eliminado o no no tiene ningún efecto.
Grzegorz Sławecki

en la página aspx puede agregar una llamada ajax para llamar a la vista que desea mostrar. Ahorra tener que duplicar el código
Mike

71

Resolví esto creando un ExceptionFilter y registrando el error allí en lugar de Application_Error. Todo lo que necesita hacer es agregar una llamada en 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());
}

66
+1 Si esta respuesta funciona, seguro que me parece la forma más elegante y quizás intencionada (por el marco MVC) para manejar los errores.
Matt Hamsmith

2
Esto funciona perfectamente para mí y es mucho más ordenado que la respuesta aceptada aquí. ¡Agradable!
Alex Warren

3
Esto funcionará para resolver el problema de las excepciones de registro. ¿Cómo combinaste esto con mostrar páginas de error personalizadas?
Jesse Webb

3
Intenté esto y funcionó bien, excepto en el caso de los 404. Para los 404, no mostraría la vista Errores.cshtml, solo me daría un YSoD. Si no se requieren 404 personalizados, ¡esta solución es definitivamente más limpia!
Jesse Webb

1
Me gusta. Funciona con 'CustomErrors = On'
Illidan

36

Encontré un artículo que describe una forma mucho más limpia de crear páginas de error personalizadas en una aplicación web MVC3 que no impide la capacidad de registrar las excepciones.

La solución es usar el <httpErrors>elemento de la <system.webServer>sección.

Configuré mi Web.config así ...

<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é customErrorstener 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 que use 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 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 sentí que estaba luchando contra el marco MVC; ¡esta solución realmente lo aprovecha!


66
Vale la pena mencionar que creo que esta solución solo funciona en IIS 7 y posteriores; El elemento httpErrors se agregó recientemente.
Jesse Webb

Esta respuesta no funcionó para mí, me tiene, me puso un azul horrible: HTTP Error 500.0 - Internal Server Error The page cannot be displayed because an internal server error has occurred. pantalla de error, no es mi /Error/Indexpágina solicitada
Serj Sagan

@SerjSagan Acabo de probar esto en un nuevo proyecto para probar los pasos y funciona bien. ¿Está utilizando IIS, IIS Express o VS Dev Server? Esto no funcionará en VS Dev Server. Cuando configuré mi proyecto para usar VS Dev Server, no IIS, noté errores de pantalla amarilla en lugar de páginas de error personalizadas.
Jesse Webb

1
@SerjSagan Pero parece que está recibiendo errores de pantalla azul de IIS, a diferencia de los errores clásicos de pantalla amarilla. Esto me hace suponer que está utilizando alguna forma de IIS. Si es así, lea el artículo al que me vinculé en la primera oración, específicamente la última sección titulada: "Algunas notas importantes". Dice:To able to overwrite global settings in global IIS settings (file: C:\Windows\System32\inetsrv\config \applicationHost.config) should be: <section name="httpErrors" overrideModeDefault="Allow" />
Jesse Webb

3
@SerjSagan Cambiando errorMode a DetailLocalOnly o Custom mostrará su página.
Daniel P

8

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.csde App_Startcarpeta.


1

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 cualquier dependencia que ya haya inyectado en su controlador base con su contenedor IoC.

Todavía puede usar su IoC con un IExceptionFilter, pero es un poco más complicado configurar sus enlaces.


0

Hasta donde yo sé, está pasando el control a la página especificada en el parámetro url y su notificación de evento se quedarí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


Gracias a Chris, leí la documentación y sugiere que se invocará Application_Error cuando ocurra un error no controlado (que espero) y que Server.ClearError () no se llame la sección web.config customErrors será el punto de control final. Sin embargo, habilitar customErrors es lo que impide que Application_Error se active.
WDuffy

La documentación a la que se vinculó está hablando de la aplicación ASP.NET y parece que las aplicaciones web MVC3 se comportan de manera diferente.
Jesse Webb

0

Para evitar esto, terminé dejando deshabilitados los clientes y manejando todos los errores del evento Application_Error en global.asax. Es un poco complicado con MVC ya que no quería devolver una redirección 301, quería devolver 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 enumerados 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();
    }

}

Intenté tu solución pero no funcionó para mí. Con las líneas Response.StatusCode = ###;, mostraba las páginas de error MVC incorporadas en C:\inetpub\custerr\en-US. Tampoco me gustó la idea de invocar manualmente HttpHandlers o Controllers desde mi método Application_Error (). Sin embargo, me alegra que hayas encontrado una solución a tu problema, sé qué tipo de dolores de cabeza me ha causado.
Jesse Webb

0

Esta entrada de blog me ayudó a:

http://asp-net.vexedlogic.com/2011/04/23/asp-net-maximum-request-length-exceeded/

Si está utilizando IIS 7.0 o superior, puede alterar su archivo Web.config para manejar 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 del 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 maxAllowedContentLengthse especifica en bytes. Esto es diferente de la maxRequestLengthconfiguración que encuentra en la <system.web>sección, que se especifica en kilobytes.

<system.web>
  <httpRuntime maxRequestLength="10240" />
</system.web>

También tenga en cuenta que el pathatributo debe ser una ruta absoluta cuando responseModees Redirect, así que anteponga el nombre del directorio virtual, si es relevante. Las respuestas informativas de Jesse Webb muestran cómo hacer esto conresponseMode="ExecuteURL" , y creo que ese enfoque también funcionaría bien.

Este enfoque no funciona si está desarrollando utilizando 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.


0

Estaba teniendo el mismo problema donde Application_Error()no me golpeaban. Lo intenté todo, hasta que finalmente supe lo que estaba sucediendo. ¡Tenía un código personalizado en un evento de ELMAH que estaba agregando JSON al correo electrónico que envía, y había un error nulo allí!

La corrección del error interno permitió que el código continuara en el Application_Error()evento como se esperaba.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.