Как заставить ELMAH работать с атрибутом ASP.NET MVC [HandleError]?
Я пытаюсь использовать ELMAH для регистрации ошибок в моем приложении ASP.NET MVC, однако, когда я использую атрибут [HandleError] на моих контроллерах, ELMAH не регистрирует ошибки при их возникновении.
Как я догадываюсь, потому что ELMAH регистрирует только необработанные ошибки, а атрибут [HandleError] обрабатывает ошибку, поэтому нет необходимости регистрировать ее.
Как изменить или как изменить атрибут, чтобы ELMAH мог знать, что произошла ошибка, и зарегистрировать ее...
Изменить: Позвольте мне убедиться, что все понимают, я знаю, что я могу изменить атрибут, который не вопрос, который я задаю... ELMAH игнорируется при использовании атрибута handleerror, что означает, что он не увидит, что произошла ошибка, потому что он был обработан уже по атрибуту... То, что я спрашиваю, есть ли способ заставить ELMAH увидеть ошибку и записать ее в журнал, даже если атрибут обработал ее... Я искал и не вижу никаких методов для вызова, чтобы заставить его регистрировать Ошибка....
8 ответов
Вы можете подкласс 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
экземпляры действуют, тогда дублирование регистрации не происходит, но приведенные выше два примера должны начать работу.
Извините, но я думаю, что принятый ответ является излишним. Все, что вам нужно сделать, это:
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());
}
Теперь в 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. И я забыл упомянуть: в Эльме нет дубликатов.
Вы можете взять приведенный выше код и сделать еще один шаг вперед, введя собственную фабрику контроллеров, которая добавляет атрибут HandleErrorWithElmah в каждый контроллер.
Для получения дополнительной информации ознакомьтесь с серией моих блогов о входе в систему MVC. Первая статья посвящена настройке Elmah и работе на MVC.
В конце статьи есть ссылка на загружаемый код. Надеюсь, это поможет.
Совершенно альтернативное решение - не использовать MVC HandleErrorAttribute
и вместо этого полагаться на обработку ошибок ASP.Net, с которой Elmah предназначен для работы.
Вам нужно удалить глобальный по умолчанию HandleErrorAttribute
из App_Start\FilterConfig (или Global.asax), а затем настройте страницу ошибки в вашем файле Web.config:
<customErrors mode="RemoteOnly" defaultRedirect="~/error/" />
Обратите внимание, что это может быть URL с маршрутизацией MVC, поэтому приведенное выше перенаправит на ErrorController.Index
действие при возникновении ошибки.
Я новичок в 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>
Это просто!
Для меня было очень важно, чтобы регистрация электронной почты работала. Через некоторое время я обнаружил, что для этого нужно всего лишь 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);
[...]
}
[...]
}
Надеюсь, это кому-нибудь поможет:)
Это именно то, что мне нужно для конфигурации моего сайта MVC!
Я добавил небольшую модификацию OnException
метод для обработки нескольких HandleErrorAttribute
случаи, предложенные Атифом Азизом:
имейте в виду, что вам, возможно, придется позаботиться о том, что если несколько
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);
}