Come far funzionare ELMAH con l'attributo ASP.NET MVC [HandleError]?


564

Sto cercando di utilizzare ELMAH per registrare gli errori nella mia applicazione ASP.NET MVC, tuttavia quando utilizzo l'attributo [HandleError] sui miei controller ELMAH non registra alcun errore quando si verificano.

Come immagino sia perché ELMAH registra solo errori non gestiti e l'attributo [HandleError] gestisce l'errore, quindi non è necessario registrarlo.

Come posso modificare o come fare per modificare l'attributo in modo che ELMAH possa sapere che si è verificato un errore e registrarlo.

Modifica: Fammi accertare che tutti capiscano, so che posso modificare l'attributo che non è la domanda che sto ponendo ... ELMAH viene escluso quando si utilizza l'attributo handleerror, il che significa che non vedrà che si è verificato un errore perché è stato gestito già dall'attributo ... Quello che sto chiedendo è un modo per fare in modo che ELMAH veda l'errore e lo registri anche se l'attributo lo ha gestito ... Ho cercato in giro e non vedo alcun metodo da chiamare per costringerlo ad accedere l'errore....


12
Wow, spero che Jeff o Jared rispondano a questa domanda. Stanno usando ELMAH per Stackoverflow;)
Jon Limjap,

11
Hmm, strano - non usiamo HandleErrorAttribute - Elmah è installato nella sezione <modules> del nostro web.config. Ci sono vantaggi nell'uso di HandleErrorAttribute?
Jarrod Dixon

9
@Jarrod - sarebbe bello vedere cosa è "personalizzato" sulla tua forcella ELMAH.
Scott Hanselman,

3
@dswatik È inoltre possibile impedire i reindirizzamenti impostando redirectMode su ResponseRewrite in web.config. Vedi blog.turlov.com/2009/01/…
Pavel Chuchuva il

6
Ho continuato a imbattermi nella documentazione e nei post sul web parlando dell'attributo [HandleError] e di Elmah, ma non vedevo il comportamento che risolve (ad esempio, Elmah non registra l'errore "gestito") quando configuro il caso fittizio. Questo perché a partire da Elmah.MVC 2.0.x questo handleErrorAttribute personalizzato non è più necessario; è incluso nel pacchetto nuget.
plyawn,

Risposte:


503

È possibile sottoclassare HandleErrorAttributee sovrascrivere il suo OnExceptionmembro (non è necessario copiarlo) in modo che registri l'eccezione con ELMAH e solo se l'implementazione di base lo gestisce. La quantità minima di codice necessaria è la seguente:

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'implementazione di base viene prima invocata, dando la possibilità di contrassegnare l'eccezione come gestita. Solo allora viene segnalata l'eccezione. Il codice sopra è semplice e può causare problemi se utilizzato in un ambiente in cui HttpContextpotrebbe non essere disponibile, ad esempio test. Di conseguenza, vorrai un codice più difensivo (al costo di essere leggermente più lungo):

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

Questa seconda versione proverà a utilizzare prima la segnalazione degli errori da ELMAH, che coinvolge la pipeline completamente configurata come registrazione, mailing, filtro e cosa hai. In caso contrario, tenta di vedere se l'errore deve essere filtrato. In caso contrario, l'errore viene semplicemente registrato. Questa implementazione non gestisce le notifiche di posta. Se l'eccezione può essere segnalata, verrà inviata un'e-mail se configurata per farlo.

Potrebbe anche essere necessario prestare attenzione al fatto che, se HandleErrorAttributesono attive più istanze, la registrazione duplicata non si verifica, ma i due esempi precedenti dovrebbero iniziare.


1
Eccellente. Non stavo affatto cercando di implementare Elmah. Stavo solo cercando di collegare la mia segnalazione di errori che ho usato per anni in un modo che funziona bene con MVC. Il tuo codice mi ha dato un punto di partenza. +1
Steve Wortham,

18
Non è necessario sottoclassare HandleErrorAttribute. Puoi semplicemente avere un'implementazione IExceptionFilter e averla registrata insieme a HandleErrorAttribute. Inoltre non capisco perché è necessario avere un fallback nel caso in cui ErrorSignal.Raise (..) fallisca. Se la pipeline non è configurata correttamente, dovrebbe essere riparata. Per un punto di controllo IExceptionFilter da 5 linee 4. qui - ivanz.com/2011/05/08/…
Ivan Zlatev

5
Per favore, puoi commentare la risposta qui sotto di @IvanZlatev per quanto riguarda l'applicabilità, le carenze, ecc. Le persone stanno commentando che è più facile / più breve / più semplice e raggiunge lo stesso della tua risposta e come tale dovrebbe essere contrassegnato come la risposta corretta. Sarebbe bello avere la tua prospettiva su questo e ottenere un po 'di chiarezza con queste risposte.
Andrew,

7
È ancora rilevante o ELMAH.MVC gestisce questo?
Romias,

2
Vorrei anche sapere se è ancora rilevante nella versione odierna
refactor

299

Scusa, ma penso che la risposta accettata sia eccessiva. Tutto quello che devi fare è questo:

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

e quindi registrarlo (l'ordine è importante) in Global.asax.cs:

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

3
+1 Molto bello, non è necessario estendere il HandleErrorAttribute, non è necessario eseguire OnExceptionl' override BaseController. Questo è supponiamo che la risposta accettata.
CallMeLaNN,

1
@bigb Penso che dovresti racchiudere l'eccezione nel tuo tipo di eccezione per aggiungere elementi al messaggio di eccezione, ecc. (ad esempio, new UnhandledLoggedException(Exception thrown)che aggiunge qualcosa al Messageprecedente prima di restituirlo.
Ivan Zlatev,

23
Atif Aziz ha creato ELMAH, vorrei andare con la sua risposta
jamiebarrow il

48
@jamiebarrow Non me ne sono reso conto, ma la sua risposta ha ~ 2 anni e probabilmente l'API è stata semplificata per supportare i casi d'uso della domanda in un modo più breve e autonomo.
Ivan Zlatev,

6
@Ivan Zlatev non riesce davvero a lavorare ElmahHandledErrorLoggerFilter()elmah semplicemente registrando errori non gestiti, ma non gestiti. Ho registrato i filtri nell'ordine corretto come hai detto tu, qualche pensiero?
kuncevic.dev,

14

Ora c'è un pacchetto ELMAH.MVC in NuGet che include una soluzione migliorata di Atif e anche un controller che gestisce l'interfaccia elmah all'interno del routing MVC (non è più necessario utilizzare quell'axd)
Il problema con quella soluzione (e con tutte quelle qui ) è che in un modo o nell'altro il gestore degli errori elmah sta effettivamente gestendo l'errore, ignorando ciò che potresti voler impostare come tag customError o tramite ErrorHandler o il tuo gestore degli errori
La migliore soluzione IMHO è creare un filtro che agirà alla fine di tutti gli altri filtri e registrare gli eventi che sono già stati gestiti. Il modulo elmah dovrebbe occuparsi della registrazione degli altri errori non gestiti dall'applicazione. Ciò consentirà inoltre di utilizzare il monitoraggio dello stato e tutti gli altri moduli che possono essere aggiunti ad asp.net per esaminare gli eventi di errore

Ho scritto questo osservando con il riflettore ErrorHandler all'interno di 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;
   }
}

Ora, nella configurazione del filtro, vuoi fare qualcosa del genere:

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

Nota che ho lasciato un commento lì per ricordare alle persone che se vogliono aggiungere un filtro globale che gestirà effettivamente l'eccezione dovrebbe andare PRIMA di quest'ultimo filtro, altrimenti ti imbatterai nel caso in cui l'eccezione non gestita verrà ignorata da ElmahMVCErrorFilter perché non è stato gestito e dovrebbe essere registrato dal modulo Elmah, ma il filtro successivo contrassegna l'eccezione come gestita e il modulo la ignora, determinando che l'eccezione non la trasformi mai in elmah.

Ora, assicurati che le impostazioni delle app per elmah nel tuo webconfig siano simili a queste:

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

Quello importante qui è "elmah.mvc.disableHandleErrorFilter", se questo è falso userà il gestore all'interno di elmah.mvc che gestirà effettivamente l'eccezione usando il HandleErrorHandler predefinito che ignorerà le tue impostazioni customError

Questa configurazione ti consente di impostare i tuoi tag ErrorHandler in classi e viste, registrando comunque quegli errori attraverso ElmahMVCErrorFilter, aggiungendo una configurazione CustomError al tuo web.config attraverso il modulo elmah, persino scrivendo i tuoi Error Handlers. L'unica cosa che devi fare è ricordare di non aggiungere filtri che gestiranno effettivamente l'errore prima del filtro elmah che abbiamo scritto. E ho dimenticato di menzionare: nessun duplicato in elmah.


7

Puoi prendere il codice sopra e fare un ulteriore passo introducendo una factory di controller personalizzata che inietta l'attributo HandleErrorWithElmah in ogni controller.

Per ulteriori informazioni, consulta la mia serie di blog sulla registrazione in MVC. Il primo articolo riguarda l'installazione e l'esecuzione di Elmah per MVC.

C'è un link al codice scaricabile alla fine dell'articolo. Spero che aiuti.

http://dotnetdarren.wordpress.com/


6
Mi sembra che sarebbe molto più semplice incollarlo su una classe di controller di base!
Nathan Taylor,

2
La serie di Darren sopra sulla registrazione e la gestione delle eccezioni merita la lettura !!! Molto accurato!
Ryan Anderson,

6

Sono nuovo in ASP.NET MVC. Ho riscontrato lo stesso problema, il seguente è il mio realizzabile nel mio Erorr.vbhtml (funziona se è necessario solo registrare l'errore utilizzando il registro 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> 

È semplicemente!


Questa è di gran lunga la soluzione più semplice. Non c'è bisogno di scrivere o registrare gestori e cose personalizzate. Funziona bene per me
ThiagoAlves,

3
Verrà ignorato per eventuali risposte JSON / non HTML.
Craig Stuntz,

6
anche questo sta facendo funzionalità di livello di servizio in una vista. Non appartiene qui.
Trevor de Koekkoek,

6

Una soluzione completamente alternativa è quella di non utilizzare MVC HandleErrorAttributee di affidarsi invece alla gestione degli errori ASP.Net, con cui Elmah è progettato per funzionare.

È necessario rimuovere il globale predefinito HandleErrorAttributeda App_Start \ FilterConfig (o Global.asax), quindi impostare una pagina di errore in Web.config:

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

Nota, questo può essere un URL indirizzato MVC, quindi quanto sopra reindirizzerebbe ErrorController.Indexall'azione quando si verifica un errore.


Questa è di gran lunga la soluzione più semplice e il reindirizzamento predefinito può essere un'azione MVC :)
Jeremy Cook,

3
Ciò reindirizzerà per altri tipi di richieste, come JSON ecc. - Non va bene.
Zvolkov,

5

Per me è stato molto importante far funzionare la registrazione e-mail. Dopo un po 'di tempo scopro che questo richiede solo 2 righe di codice in più nell'esempio 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);
        [...]
    }
    [...]
}

Spero che questo possa aiutare qualcuno :)


2

Questo è esattamente ciò di cui avevo bisogno per la configurazione del mio sito MVC!

Ho aggiunto una piccola modifica al OnExceptionmetodo per gestire più HandleErrorAttributeistanze, come suggerito da Atif Aziz:

tenere presente che potrebbe essere necessario prestare attenzione al fatto che se HandleErrorAttributesono presenti più istanze, la registrazione duplicata non si verifica.

Controllo semplicemente context.ExceptionHandledprima di invocare la classe base, solo per sapere se qualcun altro ha gestito l'eccezione prima dell'attuale gestore.
Funziona per me e inserisco il codice nel caso in cui qualcun altro ne abbia bisogno e per chiedere se qualcuno sa se ho trascurato qualcosa.

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

Sembra che tu non abbia un'istruzione "if" attorno al richiamo di base.OnException () .... E (exceptionHandledByPreviousHandler ||! Context.ExceptionHandled || ...) si annullano a vicenda e saranno sempre vere. Mi sto perdendo qualcosa?
Joelvh,

Innanzitutto controllo se un altro gestore, invocato prima dell'attuale, ha gestito l'eccezione e memorizzo il risultato nella variabile: exceptionHandlerdByPreviousHandler. Quindi do la possibilità al gestore corrente di gestire l'eccezione stessa: base.OnException (contesto).
ilmatte,

Innanzitutto controllo se un altro gestore, invocato prima dell'attuale, ha gestito l'eccezione e memorizzo il risultato nella variabile: exceptionHandlerdByPreviousHandler. Quindi do la possibilità al gestore corrente di gestire l'eccezione stessa: base.OnException (contesto). Se l'eccezione non è stata precedentemente gestita, può essere: 1 - È gestita dal gestore corrente, quindi: exceptionHandledByPreviousHandler = false e! Context.ExceptionHandled = false 2 - Non è gestita dal gestore corrente e: exceptionHandledByPreviousHandler = false e! Context. Eccezione Gestito vero. Verrà registrato solo il caso 1.
ilmatte,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.