web-dev-qa-db-fra.com

Comment obliger ELMAH à utiliser l'attribut ASP.NET MVC [HandleError]?

J'essaie d'utiliser ELMAH pour consigner les erreurs dans mon application ASP.NET MVC. Toutefois, lorsque j'utilise l'attribut [HandleError] sur mes contrôleurs, ELMAH ne consigne aucune erreur lorsqu'elle se produit.

Comme je le devine, c'est parce qu'ELMAH enregistre uniquement les erreurs non gérées et que l'attribut [HandleError] traite l'erreur, il est donc inutile de la consigner.

Comment puis-je modifier ou comment modifier l'attribut afin qu'ELMAH puisse savoir qu'il y a eu une erreur et la consigner?.

Edit: Laissez-moi m'assurer que tout le monde comprend, je sais que je peux modifier l'attribut, ce n'est pas la question que je pose ... ELMAH est ignoré lors de l'utilisation de l'attribut handleerror, ce qui signifie qu'il ne verra pas cela était une erreur car elle était déjà gérée par l'attribut ... Ce que je demande, c'est un moyen de faire en sorte que ELMAH voie l'erreur et la consigne, même si l'attribut l'a traitée ... J'ai cherché autour et je n'ai pas vu de méthode appeler pour le forcer à enregistrer l'erreur ....

561
dswatik

Vous pouvez sous-classer HandleErrorAttribute et remplacer son membre OnException (pas besoin de copier) pour qu'il enregistre l'exception avec ELMAH et uniquement si l'implémentation de base le gère. La quantité minimale de code dont vous avez besoin est la suivante:

using System.Web.Mvc;
using Elmah;

public class HandleErrorAttribute : System.Web.Mvc.HandleErrorAttribute
{
    public override void OnException(ExceptionContext context)
    {
        base.OnException(context);
        if (!context.ExceptionHandled) 
            return;
        var httpContext = context.HttpContext.ApplicationInstance.Context;
        var signal = ErrorSignal.FromContext(httpContext);
        signal.Raise(context.Exception, httpContext);
    }
}

L'implémentation de base est d'abord appelée, ce qui lui permet de marquer l'exception comme étant traitée. Alors seulement l'exception est signalée. Le code ci-dessus est simple et peut causer des problèmes s'il est utilisé dans un environnement où HttpContext peut ne pas être disponible, tel que les tests. En conséquence, vous voudrez un code plus défensif (au prix d'un peu plus long):

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

public class HandleErrorAttribute : System.Web.Mvc.HandleErrorAttribute
{
    public override void OnException(ExceptionContext context)
    {
        base.OnException(context);
        if (!context.ExceptionHandled       // if unhandled, will be logged anyhow
            || TryRaiseErrorSignal(context) // prefer signaling, if possible
            || IsFiltered(context))         // filtered?
            return;

        LogException(context);
    }

    private static bool TryRaiseErrorSignal(ExceptionContext context)
    {
        var httpContext = GetHttpContextImpl(context.HttpContext);
        if (httpContext == null)
            return false;
        var signal = ErrorSignal.FromContext(httpContext);
        if (signal == null)
            return false;
        signal.Raise(context.Exception, httpContext);
        return true;
    }

    private static bool IsFiltered(ExceptionContext context)
    {
        var config = context.HttpContext.GetSection("elmah/errorFilter")
                        as ErrorFilterConfiguration;

        if (config == null)
            return false;

        var testContext = new ErrorFilterModule.AssertionHelperContext(
                              context.Exception, 
                              GetHttpContextImpl(context.HttpContext));
        return config.Assertion.Test(testContext);
    }

    private static void LogException(ExceptionContext context)
    {
        var httpContext = GetHttpContextImpl(context.HttpContext);
        var error = new Error(context.Exception, httpContext);
        ErrorLog.GetDefault(httpContext).Log(error);
    }

    private static HttpContext GetHttpContextImpl(HttpContextBase context)
    {
        return context.ApplicationInstance.Context;
    }
}

Cette deuxième version essaiera d’utiliser signalisation d’erreur d’ELMAH, ce qui implique le pipeline entièrement configuré, comme la journalisation, le mailing, le filtrage et ainsi de suite. À défaut, il essaie de voir si l'erreur doit être filtrée. Sinon, l'erreur est simplement consignée. Cette implémentation ne gère pas les notifications par mail. Si l'exception peut être signalée, un courrier sera envoyé s'il est configuré pour le faire.

Vous devrez peut-être également veiller à ce que si plusieurs instances HandleErrorAttribute sont en vigueur, la journalisation en double ne se produit pas, mais les deux exemples ci-dessus devraient vous aider à démarrer.

499
Atif Aziz

Désolé, mais je pense que la réponse acceptée est un excès. Tout ce que vous devez faire est ceci:

public class ElmahHandledErrorLoggerFilter : IExceptionFilter
{
    public void OnException (ExceptionContext context)
    {
        // Log only handled exceptions, because all other will be caught by ELMAH anyway.
        if (context.ExceptionHandled)
            ErrorSignal.FromCurrentContext().Raise(context.Exception);
    }
}

puis enregistrez-le (l'ordre est important) dans Global.asax.cs:

public static void RegisterGlobalFilters (GlobalFilterCollection filters)
{
    filters.Add(new ElmahHandledErrorLoggerFilter());
    filters.Add(new HandleErrorAttribute());
}
297
Ivan Zlatev

Il existe maintenant un paquet ELMAH.MVC dans NuGet qui inclut une solution améliorée d’Atif ainsi qu’un contrôleur qui gère l’interface elmah dans le routage MVC (plus besoin d’utiliser axd).
Le problème de cette solution (et de toutes les autres ici) est que, d'une manière ou d'une autre, le gestionnaire d'erreurs elmah gère réellement l'erreur, en ignorant ce que vous pourriez vouloir configurer en tant que balise customError ou via ErrorHandler ou votre propre gestionnaire d'erreur
La meilleure solution à mon humble avis est de créer un filtre qui agira à la fin de tous les autres filtres et consignera les événements déjà traités. Le module elmah devrait se charger de consigner les autres erreurs qui ne sont pas gérées par l'application. Cela vous permettra également d'utiliser le moniteur de santé et tous les autres modules qui peuvent être ajoutés à asp.net pour examiner les événements d'erreur.

J'ai écrit cette recherche avec réflecteur sur le ErrorHandler à l'intérieur d'elmah.mvc

public class ElmahMVCErrorFilter : IExceptionFilter
{
   private static ErrorFilterConfiguration _config;

   public void OnException(ExceptionContext context)
   {
       if (context.ExceptionHandled) //The unhandled ones will be picked by the elmah module
       {
           var e = context.Exception;
           var context2 = context.HttpContext.ApplicationInstance.Context;
           //TODO: Add additional variables to context.HttpContext.Request.ServerVariables for both handled and unhandled exceptions
           if ((context2 == null) || (!_RaiseErrorSignal(e, context2) && !_IsFiltered(e, context2)))
           {
            _LogException(e, context2);
           }
       }
   }

   private static bool _IsFiltered(System.Exception e, System.Web.HttpContext context)
   {
       if (_config == null)
       {
           _config = (context.GetSection("elmah/errorFilter") as ErrorFilterConfiguration) ?? new ErrorFilterConfiguration();
       }
       var context2 = new ErrorFilterModule.AssertionHelperContext((System.Exception)e, context);
       return _config.Assertion.Test(context2);
   }

   private static void _LogException(System.Exception e, System.Web.HttpContext context)
   {
       ErrorLog.GetDefault((System.Web.HttpContext)context).Log(new Elmah.Error((System.Exception)e, (System.Web.HttpContext)context));
   }


   private static bool _RaiseErrorSignal(System.Exception e, System.Web.HttpContext context)
   {
       var signal = ErrorSignal.FromContext((System.Web.HttpContext)context);
       if (signal == null)
       {
           return false;
       }
       signal.Raise((System.Exception)e, (System.Web.HttpContext)context);
       return true;
   }
}

Maintenant, dans votre configuration de filtre, vous voulez faire quelque chose comme ceci:

    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        //These filters should go at the end of the pipeline, add all error handlers before
        filters.Add(new ElmahMVCErrorFilter());
    }

Notez que j’ai laissé un commentaire pour rappeler aux utilisateurs que s’ils veulent ajouter un filtre global qui gérera réellement l’exception, il convient de le placer AVANT ce dernier filtre, sinon vous rencontrerez le cas où l’exception non gérée sera ignorée par ElmahMVCErrorFilter car il n'a pas été traité et il devrait être consigné par le module Elmah, mais le filtre suivant marque l'exception comme étant gérée et le module l'ignore, ce qui a pour résultat que l'exception ne sera jamais transformée en elmah.

Maintenant, assurez-vous que les appsettings pour elmah dans votre webconfig ressemblent à ceci:

<add key="elmah.mvc.disableHandler" value="false" /> <!-- This handles elmah controller pages, if disabled elmah pages will not work -->
<add key="elmah.mvc.disableHandleErrorFilter" value="true" /> <!-- This uses the default filter for elmah, set to disabled to use our own -->
<add key="elmah.mvc.requiresAuthentication" value="false" /> <!-- Manages authentication for elmah pages -->
<add key="elmah.mvc.allowedRoles" value="*" /> <!-- Manages authentication for elmah pages -->
<add key="elmah.mvc.route" value="errortracking" /> <!-- Base route for elmah pages -->

Le plus important ici est "elmah.mvc.disableHandleErrorFilter". Si cela est faux, il utilisera le gestionnaire dans elmah.mvc qui gérera réellement l’exception en utilisant le paramètre par défaut HandleErrorHandler qui ignorera vos paramètres customError.

Cette configuration vous permet de définir vos propres balises ErrorHandler dans des classes et des vues, tout en enregistrant ces erreurs via ElmahMVCErrorFilter, en ajoutant une configuration customError à votre web.config via le module elmah, même en écrivant vos propres gestionnaires d’erreurs. La seule chose à faire est de ne pas ajouter de filtres permettant de gérer l'erreur avant le filtre elmah que nous avons écrit. Et j'ai oublié de mentionner: pas de doublons à Elmah.

14
Raul Vejar

Vous pouvez prendre le code ci-dessus et aller encore plus loin en introduisant une fabrique de contrôleurs personnalisés qui injecte l'attribut HandleErrorWithElmah dans chaque contrôleur.

Pour plus d'informations, consultez ma série de blogs sur la connexion à MVC. Le premier article explique comment installer et exécuter Elmah pour MVC.

Il existe un lien vers le code téléchargeable à la fin de l'article. J'espère que ça t'as aidé.

http://dotnetdarren.wordpress.com/

7
Darren

Une solution totalement alternative consiste à ne pas utiliser MVC HandleErrorAttribute, mais à compter sur le traitement des erreurs ASP.Net, avec lequel Elmah est conçu pour fonctionner.

Vous devez supprimer le HandleErrorAttribute global global par défaut de App_Start\FilterConfig (ou Global.asax), puis configurer une page d'erreur dans votre Web.config:

<customErrors mode="RemoteOnly" defaultRedirect="~/error/" />

Notez qu'il peut s'agir d'une URL routée par MVC. Par conséquent, la procédure ci-dessus serait redirigée vers l'action ErrorController.Index en cas d'erreur.

6
Ross McNab

Je suis nouveau dans ASP.NET MVC. J'ai rencontré le même problème, ce qui suit est mon réalisable dans mon Erorr.vbhtml (cela fonctionne si vous avez seulement besoin de consigner l'erreur en utilisant le journal Elmah)

@ModelType System.Web.Mvc.HandleErrorInfo

    @Code
        ViewData("Title") = "Error"
        Dim item As HandleErrorInfo = CType(Model, HandleErrorInfo)
        //To log error with Elmah
        Elmah.ErrorLog.GetDefault(HttpContext.Current).Log(New Elmah.Error(Model.Exception, HttpContext.Current))
    End Code

<h2>
    Sorry, an error occurred while processing your request.<br />

    @item.ActionName<br />
    @item.ControllerName<br />
    @item.Exception.Message
</h2> 

C'est tout simplement!

6
user716264

Pour moi, il était très important que la journalisation des e-mails fonctionne correctement. Après un certain temps, je découvre que cela ne nécessite que 2 lignes de code supplémentaires dans l'exemple Atif.

public class HandleErrorWithElmahAttribute : HandleErrorAttribute
{
    static ElmahMVCMailModule error_mail_log = new ElmahMVCMailModule();

    public override void OnException(ExceptionContext context)
    {
        error_mail_log.Init(HttpContext.Current.ApplicationInstance);
        [...]
    }
    [...]
}

J'espère que cela aidera quelqu'un :)

5
Komio

C'est exactement ce dont j'avais besoin pour la configuration de mon site MVC!

J'ai ajouté une petite modification à la méthode OnException pour gérer plusieurs instances HandleErrorAttribute, comme suggéré par Atif Aziz:

n'oubliez pas que vous devrez peut-être veiller à ce que si plusieurs instances HandleErrorAttribute sont en vigueur, la journalisation en double ne se produit pas.

Je vérifie simplement context.ExceptionHandled avant d'invoquer la classe de base, simplement pour savoir si quelqu'un d'autre a géré l'exception avant le gestionnaire actuel.
Cela fonctionne pour moi et je poste le code au cas où quelqu'un d'autre en aurait besoin et pour demander si quelqu'un sait si j'ai oublié quelque chose.

J'espère que c'est utile:

public override void OnException(ExceptionContext context)
{
    bool exceptionHandledByPreviousHandler = context.ExceptionHandled;

    base.OnException(context);

    Exception e = context.Exception;
    if (exceptionHandledByPreviousHandler
        || !context.ExceptionHandled  // if unhandled, will be logged anyhow
        || RaiseErrorSignal(e)        // prefer signaling, if possible
        || IsFiltered(context))       // filtered?
        return;

    LogException(e);
}
2
ilmatte