Подтвердить что ты не робот

MVC [HandleError] HandleErrorAttribute вызывается дважды при использовании глобального журнала

В веб-приложении MVC3 я использовал

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

применить глобальную обработку ошибок, когда пользователю было показано представление "Ошибка", если произошло необработанное исключение.

Для одного конкретного представления я также хотел, чтобы отображалось другое представление ошибки, если возникло необработанное исключение, украсив метод с помощью [HandleError(View = "SpecialError")]. Это отлично работает.

Затем я хотел добавить глобальный журнал необработанных исключений. Я создал собственный атрибут HandleError с кодом регистрации:

public class MyHandleErrorAttribute : HandleErrorAttribute
    {
        public override void OnException(ExceptionContext context)
        {
            // Write to log code
            base.OnException(context);
        }
    }

И обновленные RegGlobalFilters и украшение метода, чтобы использовать это имя атрибута вместо. Это работает вообще, но когда исключение возникает в методе, украшенном MyHandleError(View = "SpecialError")], метод OnException вызывается дважды. Первоначально предполагалось, что декорирование метода с помощью этого атрибута заменяет глобальный обработчик, но кажется, что оно просто добавляется (что имеет больше смысла, но это не то, что я хочу). Вызывая OnException дважды, одно и то же исключение записывается дважды, что не должно происходить. Я не думаю, что OnException вызывается дважды, потому что это настраиваемый атрибут. Я считаю, что это происходит и со стандартным атрибутом HandleError, но теперь это просто видно, поскольку я создаю его запись.

В конечном счете, я хочу регистрировать все необработанные исключения (один раз), сохраняя функции, предлагаемые [HandleError], в частности, устанавливая разные представления для определенных исключений метода. Есть ли чистый способ сделать это?

4b9b3361

Ответ 1

Я считаю, что сам нашел для себя чистое решение. Расширение HandleError показалось хорошей идеей, но теперь я думаю, что это был шаг в неправильном направлении. Я не хотел обрабатывать какие-либо ошибки по-разному, просто пишите исключения для журнала один раз, прежде чем HandleError их подбирает. Из-за этого, HandleError по умолчанию может быть оставлен на месте как есть. Хотя OnException можно вызывать несколько раз, он, по-видимому, является полностью мягким в стандартной реализации HandleErrorAttribute.

Вместо этого я создал фильтр регистрации исключений:

public class LoggedExceptionFilter : IExceptionFilter
    {
        public void OnException(ExceptionContext filterContext)
        {
            // logging code
        }
    }

Он не нуждается в наследовании от FilterAttribute, поскольку он только что зарегистрирован один раз в пределах RegisterGlobalFilters вместе с HandleErrorAttribute.

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

Это позволяет записывать исключения, не меняя стандартные [HandleError] функции

Ответ 2

Попробуйте это,

public class MyHandleErrorAttribute : HandleErrorAttribute
{
    public override void OnException(ExceptionContext context)
    {
         var exceptionHandled = context.ExceptionHandled;

         base.OnException(context);                          

         if(!exceptionHandled && context.ExceptionHandled)
           // log the error.
    }
}

Ответ 3

Вы можете создать пользовательский IFilterProvider, который будет проверять, был ли фильтр уже применен к этому действию:

public class MyFilterProvider : IFilterProvider
{
    public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
    {
        if (!actionDescriptor.GetFilterAttributes(true).Any(a => a.GetType() == typeof(MyHandleErrorAttribute)))
        {
            yield return new Filter(new MyHandleErrorAttribute(), FilterScope.Global, null);
        }
    }
}

Затем вместо регистрации вашего фильтра с помощью GlobalFilterCollection, вы зарегистрируете поставщика фильтра в Application_Start()

FilterProviders.Providers.Add(new MyFilterProvider());

Альтернативно (аналогично предложению @Mark) вы можете явно установить свойство ExceptionHandled ExceptionContext

public class MyHandleErrorAttribute : HandleErrorAttribute
{
    public override void OnException(ExceptionContext context)
    {
        if(context.ExceptionHandled) return;

        // Write to log code
        base.OnException(context);
        context.ExceptionHandled = true;
    }
}

Ответ 4

Я действительно нашел решение, чтобы метод OnException дважды срабатывал. Если вы используете метод FilterConfig.RegisterGlobalFilters(), закомментируйте регистрацию HandleErrorAttribute:

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        //filters.Add(new HandleErrorAttribute());
    }
}

Фактически, я также использовал встроенный HandleErrorAttribute, не регистрируя его, и он работал нормально. Мне нужно только настроить пользовательские ошибки:

 <system.web>
    <customErrors mode="On" />
 </system.web>