Как заставить ELMAH работать с атрибутом ASP.NET MVC [HandleError]? - PullRequest
559 голосов
/ 20 апреля 2009

Я пытаюсь использовать ELMAH для регистрации ошибок в моем приложении ASP.NET MVC, однако при использовании атрибута [HandleError] на моих контроллерах ELMAH не регистрирует ошибки при их возникновении.

Как я догадываюсь, потому что ELMAH регистрирует только необработанные ошибки, а атрибут [HandleError] обрабатывает ошибку, поэтому нет необходимости регистрировать ее.

Как мне изменить или как мне изменить атрибут, чтобы ELMAH мог знать, что произошла ошибка, и зарегистрировать ее ...

Редактировать: Позвольте мне убедиться, что все понимают, я знаю, что могу изменить атрибут, который не является вопросом, который я задаю ... ELMAH обходит при использовании атрибута handleerror, означающего, что он не увидит что произошла ошибка, потому что она уже была обработана атрибутом ... Я спрашиваю, есть ли способ заставить ELMAH увидеть ошибку и зарегистрировать ее, даже если атрибут ее обработал ... Я искал и не вижу любые методы для вызова, чтобы заставить это регистрировать ошибку ....

Ответы [ 8 ]

498 голосов
/ 23 апреля 2009

Вы можете создать подкласс HandleErrorAttribute и переопределить его OnException член (не нужно копировать), чтобы регистрировать исключение с помощью ELMAH, и только если базовая реализация обрабатывает его , Минимальное количество кода, которое вам нужно, выглядит следующим образом:

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

Сначала вызывается базовая реализация, что дает возможность пометить исключение как обрабатываемое. Только тогда сигнализируется исключение. Приведенный выше код прост и может вызвать проблемы, если используется в среде, где HttpContext может быть недоступен, например при тестировании. В результате вам понадобится более защищенный код (за счет некоторой длины):

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

Эта вторая версия будет пытаться сначала использовать сигнализацию об ошибках от ELMAH, которая включает в себя полностью сконфигурированный конвейер, такой как регистрация, рассылка, фильтрация и все, что у вас есть. В противном случае он пытается увидеть, следует ли фильтровать ошибку. Если нет, ошибка просто регистрируется. Эта реализация не обрабатывает почтовые уведомления. Если об исключении может быть сообщено, то письмо будет отправлено, если настроено для этого.

Возможно, вам также придется позаботиться о том, чтобы, если действуют несколько экземпляров HandleErrorAttribute, дублирование записи не происходило, но приведенные выше два примера должны помочь вам начать работу.

297 голосов
/ 09 мая 2011

Извините, но я думаю, что принятый ответ является излишним. Все, что вам нужно сделать, это:

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

и затем зарегистрируйте его (порядок важен) в Global.asax.cs:

public static void RegisterGlobalFilters (GlobalFilterCollection filters)
{
    filters.Add(new ElmahHandledErrorLoggerFilter());
    filters.Add(new HandleErrorAttribute());
}
14 голосов
/ 24 января 2013

В NuGet теперь есть пакет ELMAH.MVC, который включает в себя улучшенное решение Atif, а также контроллер, который обрабатывает интерфейс elmah в маршрутизации MVC (больше не нужно использовать этот axd)
Проблема с этим решением (и со всеми здесь) заключается в том, что так или иначе обработчик ошибок elmah фактически обрабатывает ошибку, игнорируя то, что вы можете захотеть установить как тег customError или через ErrorHandler или ваш собственный обработчик ошибок
Лучшее решение IMHO - создать фильтр, который будет действовать в конце всех других фильтров и регистрировать события, которые уже были обработаны. Модуль elmah должен позаботиться о регистрации других ошибок, которые не обрабатываются приложением. Это также позволит вам использовать монитор работоспособности и все другие модули, которые можно добавить на asp.net, для просмотра событий ошибок

Я написал это, глядя отражателем на ErrorHandler внутри 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;
   }
}

Теперь в настройках фильтра вы хотите сделать что-то вроде этого:

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

Обратите внимание, что я оставил там комментарий, чтобы напомнить людям, что если они хотят добавить глобальный фильтр, который будет обрабатывать исключение, он должен идти ДО этого последнего фильтра, в противном случае вы столкнетесь с тем случаем, когда необработанное исключение будет игнорироваться ElmahMVCErrorFilter, потому что он не был обработан и должен регистрироваться модулем Elmah, но затем следующий фильтр помечает исключение как обработанное, а модуль игнорирует его, в результате чего исключение никогда не превращается в elmah.

Теперь убедитесь, что настройки app для elmah в вашем webconfig выглядят примерно так:

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

Важным здесь является «elmah.mvc.disableHandleErrorFilter», если это false, он будет использовать обработчик внутри elmah.mvc, который фактически обработает исключение с помощью стандартного HandleErrorHandler, который будет игнорировать ваши настройки customError

Эта настройка позволяет вам устанавливать свои собственные теги ErrorHandler в классах и представлениях, сохраняя при этом регистрацию этих ошибок через ElmahMVCErrorFilter, добавляя конфигурацию customError в ваш web.config через модуль elmah, даже записывая свои собственные обработчики ошибок. Единственное, что вам нужно сделать, - это не добавлять фильтры, которые будут обрабатывать ошибку, до того, как мы написали фильтр elmah. И я забыл упомянуть: в Эльме нет дубликатов.

7 голосов
/ 28 июля 2010

Вы можете взять приведенный выше код и сделать еще один шаг вперед, введя собственную фабрику контроллеров, которая добавляет атрибут HandleErrorWithElmah в каждый контроллер.

Для получения дополнительной информации посмотрите мою серию блогов о входе в MVC. Первая статья посвящена настройке и запуску Elmah для MVC.

В конце статьи есть ссылка на загружаемый код. Надеюсь, это поможет.

http://dotnetdarren.wordpress.com/

6 голосов
/ 01 августа 2013

Совершенно альтернативным решением является не использовать MVC HandleErrorAttribute, а вместо этого полагаться на обработку ошибок ASP.Net, с которой Elmah рассчитан на работу.

Необходимо удалить глобальный HandleErrorAttribute по умолчанию из App_Start \ FilterConfig (или Global.asax), а затем настроить страницу ошибки в файле Web.config:

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

Обратите внимание, что это может быть URL-адрес с маршрутизацией MVC, поэтому при возникновении ошибки приведенное выше перенаправит на действие ErrorController.Index.

6 голосов
/ 20 апреля 2011

Я новичок в ASP.NET MVC. Я столкнулся с той же проблемой, вот мой рабочий в моем Erorr.vbhtml (он работает, если вам нужно только зарегистрировать ошибку, используя журнал 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> 

Это просто!

5 голосов
/ 26 января 2011

Для меня было очень важно, чтобы регистрация электронной почты работала. Через некоторое время я обнаружил, что для этого нужно всего 2 строки кода в примере 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);
        [...]
    }
    [...]
}

Надеюсь, это кому-нибудь поможет:)

2 голосов
/ 07 ноября 2010

Это именно то, что мне нужно для конфигурации моего сайта MVC!

Я добавил небольшую модификацию в метод OnException для обработки нескольких HandleErrorAttribute экземпляров, как предлагает Atif Aziz:

имейте в виду, что вам, возможно, придется позаботиться о том, чтобы, если действуют несколько экземпляров HandleErrorAttribute, дублирование записи не происходило.

Я просто проверяю context.ExceptionHandled перед вызовом базового класса, просто чтобы узнать, обрабатывал ли кто-то еще исключение перед текущим обработчиком.
Это работает для меня, и я выкладываю код на случай, если кому-то еще это понадобится, и спрашиваю, знает ли кто-нибудь, пропустил ли я что-нибудь.

Надеюсь, это полезно:

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