Application_Error ne se déclenche pas lorsque customerrors = "On"

124

J'ai du code dans le global.asaxApplication_Error événement du fichier qui s'exécute lorsqu'une erreur se produit et m'envoie les détails de l'erreur par e-mail.

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

    if (error.Message != "Not Found")
    {
        // Send email here...
    }

}

Cela fonctionne bien lorsque je l'exécute dans Visual Studio, mais lorsque je publie sur notre serveur en direct, l' Application_Errorévénement ne se déclenche pas.

Après quelques tests, je peux obtenir le Application_Errortir quand je suis réglécustomErrors="Off" , mais le remettre à l' customErrors="On"arrêt empêche l'événement de se déclencher à nouveau.

Quelqu'un peut-il suggérer pourquoi Application_Errorne serait pas déclenché lorsque customErrorssont activés dans le web.config?

WDuffy
la source
J'ai exactement le même problème. J'ai aussi trouvé cette question SO: stackoverflow.com/questions/3713939/… qui suggère de mettre le serveur IIS7 en mode classique. Malheureusement, ce n’est pas une option pour nous. Quelqu'un a-t-il de meilleures solutions?
Jesse Webb
Voici une autre question connexe et ses réponses (dont aucune n'est acceptée) suggèrent de ne pas utiliser du tout Application_Error () ... stackoverflow.com/questions/1194578/…
Jesse Webb
@Gweebz J'ai publié une réponse sur la façon dont j'ai contourné cela, mais je n'ai toujours pas trouvé de documentation solide sur les raisons pour lesquelles j'obtenais ce comportement.
WDuffy
J'ai ajouté une réponse qui explique pourquoi la Application_Error()méthode n'a pas été invoquée. J'ai également expliqué ma solution finale.
Jesse Webb
Je recommande vraiment cet article afin de faire fonctionner les erreurs personnalisées.
ThomasArdal

Réponses:

133

MISE À JOUR
Puisque cette réponse fournit une solution, je ne la modifierai pas, mais j'ai trouvé un moyen beaucoup plus propre de résoudre ce problème. Voir mon autre réponse pour plus de détails ...

Réponse originale:
J'ai compris pourquoi la Application_Error()méthode n'est pas invoquée ...

Global.asax.cs

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

Par défaut (lorsqu'un nouveau projet est généré), une application MVC a une certaine logique dans le Global.asax.csfichier. Cette logique est utilisée pour mapper les routes et enregistrer les filtres. Par défaut, il n'enregistre qu'un seul filtre: un HandleErrorAttributefiltre. Lorsque customErrors est activé (ou via des requêtes distantes lorsqu'il est défini sur RemoteOnly), HandleErrorAttribute indique à MVC de rechercher une vue d'erreur et n'appelle jamais la Application_Error()méthode. Je n'ai pas pu trouver de documentation à ce sujet, mais elle est expliquée dans cette réponse sur programmers.stackexchange.com .

Pour obtenir la méthode ApplicationError () appelée pour chaque exception non gérée, supprimez simplement la ligne qui enregistre le filtre HandleErrorAttribute.

Maintenant, le problème est: Comment configurer les customErrors pour obtenir ce que vous voulez ...

La section customErrors est définie par défaut sur redirectMode="ResponseRedirect". Vous pouvez également spécifier l'attribut defaultRedirect comme une route MVC. J'ai créé un ErrorController qui était très simple et j'ai changé mon web.config pour qu'il ressemble à ceci ...

web.config

<customErrors mode="RemoteOnly" redirectMode="ResponseRedirect" defaultRedirect="~/Error">
  <error statusCode="404" redirect="~/Error/PageNotFound" />
</customErrors>

Le problème avec cette solution est qu'elle effectue une redirection 302 vers vos URL d'erreur, puis ces pages répondent avec un code d'état 200. Cela conduit Google à indexer les pages d'erreur, ce qui est mauvais. Il n'est pas non plus très conforme à la spécification HTTP. Ce que je voulais faire, ce n'était pas de rediriger et de remplacer la réponse d'origine par mes vues d'erreur personnalisées.

J'ai essayé de changer redirectMode="ResponseRewrite". Malheureusement, cette option ne prend pas en charge les routes MVC , uniquement les pages HTML statiques ou ASPX. J'ai essayé d'utiliser une page HTML statique au début, mais le code de réponse était toujours de 200, mais au moins il n'a pas redirigé. J'ai alors eu une idée de cette réponse ...

J'ai décidé d'abandonner MVC pour la gestion des erreurs. J'ai créé un Error.aspxet un PageNotFound.aspx. Ces pages étaient très simples mais elles avaient un morceau de magie ...

<script type="text/C#" runat="server">
    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        Response.StatusCode = (int) System.Net.HttpStatusCode.InternalServerError;
    }
</script>

Ce bloc indique à la page à être servie avec le code d'état correct. De grossier, sur la page PageNotFound.aspx, j'ai utilisé à la HttpStatusCode.NotFoundplace. J'ai changé mon web.config pour qu'il ressemble à ceci ...

<customErrors mode="RemoteOnly" redirectMode="ResponseRewrite" defaultRedirect="~/Error.aspx">
  <error statusCode="404" redirect="~/PageNotFound.aspx" />
</customErrors>

Tout a parfaitement fonctionné!

Résumé:

  • Supprimez la ligne: filters.Add(new HandleErrorAttribute());
  • Utiliser la Application_Error()méthode pour consigner les exceptions
  • Utiliser customErrors avec un ResponseRewrite, pointant vers des pages ASPX
  • Rendre les pages ASPX responsables de leurs propres codes d'état de réponse

Il y a quelques inconvénients que j'ai remarqués avec cette solution.

  • Les pages ASPX ne peuvent partager aucun balisage avec les modèles Razor, j'ai dû réécrire le balisage d'en-tête et de pied de page standard de notre site Web pour un aspect cohérent.
  • Les pages * .aspx sont accessibles directement en cliquant sur leurs URL

Il existe des solutions de contournement pour ces problèmes, mais je n'étais pas suffisamment préoccupé par eux pour faire un travail supplémentaire.

J'espère que cela aide tout le monde!

Jesse Webb
la source
2
+1! Vraiment bonne solution de contournement, mais quelles pourraient être les conséquences du mélange de MVC avec des pages aspx?
Diego le
2
Nous avons cela dans PROD depuis quelques mois maintenant et je n'ai pas trouvé d'impact négatif. Le seul problème était que nous devions changer notre CI et déployer pour faire une tâche AspCompile MSBUILD parce que MVC n'en a pas besoin mais quand nous avons ajouté les fichiers .as , ils en avaient besoin. Il y a peut-être d'autres problèmes mais ils ne sont pas encore apparus ...
Jesse Webb
J'ai essayé d'utiliser cette approche dans MVC4, et apparemment cela ne fonctionne que pour moi quand je l'ai <customErrors mode="Off" />. Avoir filters.Add(new HandleErrorAttribute());supprimé ou non n'a aucun effet.
Grzegorz Sławecki
sur la page aspx, vous pouvez ajouter un appel ajax pour appeler la vue que vous souhaitez afficher. Évite d'avoir à dupliquer le code
Mike
71

J'ai résolu cela en créant un ExceptionFilter et en y enregistrant l'erreur au lieu de Application_Error. Tout ce que vous avez à faire est d'ajouter un appel à dans 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());
}
marque
la source
6
+1 Si cette réponse fonctionne, cela me semble certainement être le moyen le plus élégant et peut-être le plus conçu (par le framework MVC) pour gérer les erreurs.
Matt Hamsmith
2
Cela fonctionne parfaitement pour moi et est beaucoup plus soigné que la réponse acceptée ici. Agréable!
Alex Warren
3
Cela fonctionnera pour résoudre le problème de la journalisation des exceptions. Comment avez-vous combiné cela avec l'affichage de pages d'erreur personnalisées?
Jesse Webb
3
J'ai essayé cela et cela a bien fonctionné sauf dans le cas des 404. Pour les 404, cela n'afficherait pas la vue Errors.cshtml, cela me donnerait seulement un YSoD. Si des 404 personnalisés ne sont pas nécessaires, cette solution est définitivement plus propre!
Jesse Webb
1
Je l'aime. Fonctionne avec 'CustomErrors = On'
Illidan
36

J'ai trouvé un article qui décrit une manière beaucoup plus propre de créer des pages d'erreur personnalisées dans une application Web MVC3, ce qui n'empêche pas la possibilité de consigner les exceptions.

La solution est d'utiliser l' <httpErrors>élément de la <system.webServer>section.

J'ai configuré mon Web.config comme ça ...

<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>

J'ai également configuré customErrorspour avoir mode="Off"(comme suggéré par l'article).

Cela rend les réponses remplacées par les actions d'un ErrorController. Voici ce contrôleur:

public class ErrorController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    public ActionResult NotFound()
    {
        return View();
    }
}

Les vues sont très simples, je viens d'utiliser la syntaxe standard de Razor pour créer les pages.

Cela seul devrait vous suffire pour utiliser des pages d'erreur personnalisées avec MVC.

J'avais également besoin de journalisation des exceptions, alors j'ai volé la solution de Mark d'utiliser un ExceptionFilter personnalisé ...

public class ExceptionPublisherExceptionFilter : IExceptionFilter
{
    public void OnException(ExceptionContext exceptionContext)
    {
        var exception = exceptionContext.Exception;
        var request = exceptionContext.HttpContext.Request;
        // log stuff
    }
}

La dernière chose dont vous avez besoin est d'enregistrer le filtre d'exception dans votre fichier Global.asax.cs :

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    filters.Add(new ExceptionPublisherExceptionFilter());
    filters.Add(new HandleErrorAttribute());
}

Cela semble être une solution beaucoup plus propre que ma réponse précédente et fonctionne aussi bien que je sache. Je l'aime particulièrement parce que je n'avais pas l'impression de me battre contre le framework MVC; cette solution en tire profit!

Jesse Webb
la source
6
Il est à noter que je pense que cette solution ne fonctionne que dans IIS 7 et plus récent; l'élément httpErrors n'a été ajouté que récemment.
Jesse Webb
Cette réponse n'a pas fonctionné pour moi, elle a moi, elle m'a joué un horrible bleu: HTTP Error 500.0 - Internal Server Error The page cannot be displayed because an internal server error has occurred. écran d'erreur, pas ma /Error/Indexpage demandée
Serj Sagan
@SerjSagan Je viens d'essayer cela dans un nouveau projet pour tester les étapes et cela fonctionne bien. Utilisez-vous IIS, IIS Express ou VS Dev Server? Cela ne fonctionnera pas dans VS Dev Server. Lorsque mon projet était configuré pour utiliser VS Dev Server, et non IIS, j'ai remarqué des erreurs d'écran jaune au lieu de pages d'erreur personnalisées.
Jesse Webb
1
@SerjSagan Mais il semble que vous obtenez des erreurs d'écran bleu IIS, par opposition aux erreurs classiques d'écran jaune. Cela me fait supposer que vous utilisez une forme d'IIS. Si tel est le cas, lisez l'article auquel j'ai lié dans la première phrase, en particulier la dernière section intitulée: "Quelques notes importantes". Il dit: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 Le changement de errorMode en DetailedLocalOnly ou Custom affichera votre page.
Daniel P
8

En cas d' utilisation d' 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);
        }
    }

Vous pouvez le trouver dans FilterConfig.csde App_Startdossier.

Développeur
la source
1

J'aime la réponse de Mark avec le ExceptionFilter, mais une autre option, si tous vos contrôleurs dérivent du même contrôleur de base, consiste simplement à remplacer OnException dans votre contrôleur de base. Vous pouvez y faire votre journalisation et vos e-mails. Cela a l'avantage de pouvoir utiliser toutes les dépendances que vous avez déjà injectées dans votre contrôleur de base avec votre conteneur IoC.

Vous pouvez toujours utiliser votre IoC avec un IExceptionFilter, mais c'est un peu plus délicat de configurer vos liaisons.

Tim Hardy
la source
0

Pour autant que je sache, vous passez le contrôle sur la page spécifiée dans le paramètre url et votre notification d'événement se trouverait ici, plutôt que Application_Error

<customErrors defaultRedirect="myErrorPage.aspx"
              mode="On">
</customErrors>

Vous trouverez de nombreuses informations ici: http://support.microsoft.com/kb/306355

ChrisBint
la source
Merci Chris, j'ai lu la documentation et cela suggère que Application_Error sera appelée lorsqu'une erreur non gérée se produit (ce que je prévois) et que Server.ClearError () ne devrait pas être appelé la section web.config customErrors sera le point de traitement final. Cependant, l'activation de customErrors est ce qui empêche Application_Error de se déclencher.
WDuffy
La documentation à laquelle vous avez lié parle de l'application ASP.NET et il semble que les applications Web MVC3 se comportent différemment.
Jesse Webb
0

Pour contourner cela, j'ai fini par laisser customerrors désactivé et gérer toutes les erreurs de l'événement Application_Error dans global.asax. C'est un peu délicat avec MVC car je ne voulais pas renvoyer une redirection 301, je voulais renvoyer des codes d'erreur appropriés. Plus de détails peuvent être consultés sur mon blog à http://www.wduffy.co.uk/blog/using-application_error-in-asp-net-mvcs-global-asax-to-handle-errors/ mais le code final est énumérés ci-dessous...

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);
}

Et voici le contrôleur

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();
    }

}
WDuffy
la source
J'ai essayé votre solution mais cela n'a pas fonctionné pour moi. Avec les lignes Response.StatusCode = ###;, il montrait les pages d'erreur MVC intégrées à C:\inetpub\custerr\en-US. Je n'aimais pas non plus l'idée d'invoquer manuellement HttpHandlers ou Controller à partir de ma méthode Application_Error (). Je suis heureux que vous ayez trouvé une solution à votre problème, je sais quel genre de maux de tête cela m'a causé.
Jesse Webb le
0

Cette entrée de blog m'a aidé:

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

Si vous utilisez IIS 7.0 ou supérieur, vous pouvez modifier votre fichier Web.config pour gérer les demandes trop volumineuses. Il y a quelques mises en garde, mais voici un exemple:

<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>

Il y a des détails supplémentaires sur ces éléments de fichier de configuration ici:

http://www.iis.net/ConfigReference/system.webServer/security/requestFiltering/requestLimits

Le code d'état 404.13 est défini comme "Longueur du contenu trop grande". Une chose importante à noter est que le maxAllowedContentLengthest spécifié en octets. Ceci est différent du maxRequestLengthparamètre que vous trouvez dans la <system.web>section, qui est spécifié en kilo-octets.

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

Notez également que l' pathattribut doit être un chemin absolu lorsque responseModeest Redirect, donc ajoutez le nom du répertoire virtuel, le cas échéant. Les réponses informatives de Jesse Webb montrent comment faire cela responseMode="ExecuteURL", et je pense que cette approche fonctionnerait bien aussi.

Cette approche ne fonctionne pas si vous développez à l'aide de Visual Studio Development Server (Cassini, le serveur Web intégré à Visual Studio). Je suppose que cela fonctionnerait dans IIS Express, mais je ne l'ai pas testé.

Dan Jagnow
la source
0

J'avais le même problème là où je n'étais Application_Error()pas touché. J'ai tout essayé, jusqu'à ce que finalement je découvre ce qui se passait. J'avais un code personnalisé dans un événement ELMAH qui ajoutait JSON à l'e-mail qu'il envoie, et il y avait une erreur nulle là-dedans!

La correction de l'erreur interne a permis au code de continuer sur l' Application_Error()événement comme prévu.

Matt Kemp
la source