С чего начинается фильтр исключений в конструкции
Перейти к содержимому

С чего начинается фильтр исключений в конструкции

  • автор:

С чего начинается фильтр исключений в конструкции

За обработку исключения отвечает блок catch , который может иметь следующие формы:

catch < // выполняемые инструкции >
catch (тип_исключения) < // выполняемые инструкции >

Обрабатывает только те исключения, которые соответствуют типу, указаному в скобках после оператора catch. Например, обработаем только исключения типа DivideByZeroException:

try < int x = 5; int y = x / 0; Console.WriteLine($"Результат: "); > catch(DivideByZeroException)

catch (тип_исключения имя_переменной) < // выполняемые инструкции >

Обрабатывает только те исключения, которые соответствуют типу, указаному в скобках после оператора catch. А вся информация об исключении помещается в переменную данного типа. Например:

try < int x = 5; int y = x / 0; Console.WriteLine($"Результат: "); > catch(DivideByZeroException ex) < Console.WriteLine($"Возникло исключение "); >

Фильтры исключений

Фильтры исключений позволяют обрабатывать исключения в зависимости от определенных условий. Для их применения после выражения catch идет выражение when , после которого в скобках указывается условие:

catch when(условие)

В этом случае обработка исключения в блоке catch производится только в том случае, если условие в выражении when истинно. Например:

int x = 1; int y = 0; try < int result1 = x / y; int result2 = y / x; >catch (DivideByZeroException) when (y == 0) < Console.WriteLine("y не должен быть равен 0"); >catch(DivideByZeroException ex)

В данном случае будет выброшено исключение, так как y=0. Здесь два блока catch, и оба они обрабатывают исключения типа DivideByZeroException, то есть по сути все исключения, генерируемые при делении на ноль. Но поскольку для первого блока указано условие y == 0 , то именно этот блок будет обрабатывать данное исключение — условие, указанное после оператора when возвращает true.

int x = 0; int y = 1; try < int result1 = x / y; int result2 = y / x; >catch (DivideByZeroException) when (y == 0) < Console.WriteLine("y не должен быть равен 0"); >catch(DivideByZeroException ex)

В данном случае будет выброшено исключение, так как x=0. Условие первого блока catch — y == 0 теперь возвращает false. Поэтому CLR будет дальше искать соответствующие блоки catch далее и для обработки исключения выберет второй блок catch. В итоге если мы уберем второй блок catch, то исключение вообще не будет обрабатываться.

С чего начинается фильтр исключений в конструкции

Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core

Последнее обновление: 31.10.2015

Фильтры исключений срабатывают, если при выполнении метода действия будет выброшено необработанное исключение.

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

Все фильтры исключений должны применять интерфейс IExceptionFilter :

public interface IExceptionFilter

И если вдруг приложение выбрасывает необрабатываемое исключение, то фильтр вызывает метод OnException .

Передаваемый в этот метод параметр — ExceptionContext является объектом, производным от ControllerContext . Поэтому из него можно извлечь как специфичную для фильтра информацию, так и общую информацию о запросе.

В частности класс ExceptionContext обладает следующими свойствами, которые позволяют получить информацию об исключении:

Содержит информацию о методе действия, на котором было выброшено исключение

Представляет само необработанное исключение

Значение, показывающее, считается ли исключение обработанным. Если мы на фильтре помечаем его значение в true, то исключение считается обработанным

Результат метода действия, к которому применяется фильтр исключения

С помощью свойства Exception мы можем получить доступ к выбрасываемому исключению.

Установив свойство ExceptionHandled в true, фильтр тем самым помечает исключение как обработанное.

С помощью свойства Result фильтр управляет результатом действий. Общераспространенной практикой в данном случае является перенаправление пользователя на страницу ошибки или отображение ошибки на экране.

Теперь создадим простенький фильтр, который будет обрабатывать исключение IndexOutOfRangeException :

using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace Filters.Filters < public class IndexException : FilterAttribute, IExceptionFilter < public void OnException(ExceptionContext exceptionContext) < if (!exceptionContext.ExceptionHandled && exceptionContext.Exception is IndexOutOfRangeException) < exceptionContext.Result = new RedirectResult("/Content/ExceptionFound.html"); exceptionContext.ExceptionHandled = true; >> > >

Здесь в методе OnException первым делом мы проверяем, не установлено ли значение свойства ExceptionHandled . Если оно установлено в true, следовательно, какой-то другой фильтр исключений уже обработал данное исключение. Также проверяется тип исключения. Поскольку мы ловим в данном случае только исключения типа IndexOutOfRangeException , следовательно, нас только они интересуют.

Далее мы устанавливаем результат метода, к которому применен фильтр. Предполагается, что в проекте в каталоге Content у нас находится некоторая страница ExceptionFound.html, которая отображает пользователю сообщение об ошибке.

В данном случае важно пометить исключение как обработанное: exceptionContext.ExceptionHandled = true . Иначе, если мы этого не сделаем, то мы можем увидеть в браузере все диагностическое сообщение об ошибке, которое обычно посылает фреймворк в ответ клиенту.

[IndexException] public ActionResult Index()

В данном случае метод Index выбросит необработанное исключение, и оно будет объектом типа IndexOutOfRangeException , а пользователь будет перенаправлен на страницу ExceptionFound.html .

Подобным образом мы можем обработать и другие типы исключений.

HandleErrorAttribute. Встроенная обработка исключений.

Создавать свои фильтры исключений необязательно, так как во фреймворке имеется встроенная реализация интерфейса IExceptionFilter — атрибут HandleErrorAttribute. Он имеет ряд свойств, с помощью которых мы можем произвести гибкую настройку фильтра:

Представляет тип обрабатываемого исключения. По умолчанию используется System.Exception

Имя представления, которое рендерится данным фильтром. Если значение не задано, то по умолчанию используются следующие пути: /Views/Имя_контроллера/Error.cshtml или /Views/Shared/Error.cshtml

Имя используемой мастер-страницы

При обработке исключения фильтр исключений посылает статусный код HTTP 500 и генерирует указанное в свойстве View представление. Например, используем предыдущий пример с фильтром исключений, применив встроенную реализацию:

[HandleError(ExceptionType = typeof(System.IndexOutOfRangeException), View = «ExceptionFound»)] public ActionResult About()

В данном случае очевидно, что на строке mas[6] = 4; будет выброшено исключение. В режиме отладки у вас приостановится выполнение программы, тогда вы можете нажать на кнопку Continue на панели инструментов. Здесь опять мы обрабатываем исключение типа IndexOutOfRangeException, и при возникновении такового генерируем в ответ представление ExceptionFound.cshtml . Данное представление должно находиться в проекте в каталоге Views/Имя_контроллера/ .

Сразу надо сказать, что если вы хотите при разработке видеть обрабатываемые фильтром HandleErrorAttribute, то надо включить данную функциональность в файле конфигурации web.config с помощью тега :

Написание фильтра исключений

Исключение можно обработать посредством перехода на уровень обработчика исключений или путем продолжения выполнения. Вместо использования кода обработчика исключений для обработки исключения и падения можно использовать выражение фильтра для очистки проблемы. Затем, возвращая EXCEPTION_CONTINUE_EXECUTION (-1), вы можете возобновить обычный поток без очистки стека.

После некоторых исключений возобновление невозможно. Если фильтр оценивается как -1 для такого исключения, система вызывает новое исключение. При вызове RaiseException определите, будет ли продолжаться исключение.

Например, следующий код использует вызов функции в выражении фильтра : эта функция обрабатывает проблему, а затем возвращает -1 для возобновления нормального потока управления:

// exceptions_Writing_an_Exception_Filter.cpp #include int main() < int Eval_Exception( int ); __try <>__except ( Eval_Exception( GetExceptionCode( ))) < ; >> void ResetVars( int ) <> int Eval_Exception ( int n_except ) < if ( n_except != STATUS_INTEGER_OVERFLOW && n_except != STATUS_FLOAT_OVERFLOW ) // Pass on most exceptions return EXCEPTION_CONTINUE_SEARCH; // Execute some code to clean up problem ResetVars( 0 ); // initializes data to 0 return EXCEPTION_CONTINUE_EXECUTION; >

Рекомендуется использовать вызов функции в выражении фильтра всякий раз, когда фильтру нужно сделать что-либо сложное. Вычисление выражения приводит к выполнению функции, в данном случае — Eval_Exception .

Обратите внимание на использование GetExceptionCode для определения исключения. Эта функция должна вызываться внутри выражения фильтра инструкции __except . Eval_Exception не может вызываться GetExceptionCode , но он должен иметь код исключения, переданный в него.

Если исключение не вызвано переполнением при операции с целыми числами или числами с плавающей запятой, этот обработчик передает управление другому обработчику. В этом случае обработчик вызывает функцию ( ResetVars — это только пример, а не функция API), чтобы сбросить некоторые глобальные переменные. Блок инструкций __except , который в этом примере пуст, никогда не может выполняться, так как Eval_Exception никогда не возвращает EXCEPTION_EXECUTE_HANDLER (1).

Вызов функции — хороший способ работы со сложными выражениями фильтров. Удобны также две другие возможности языка C:

  • условный оператор;
  • оператор «запятая».

Условный оператор часто используется здесь. Его можно использовать для проверка для определенного кода возврата, а затем возвращать одно из двух разных значений. Например, фильтр в следующем коде распознает исключение только в том случае, если исключение: STATUS_INTEGER_OVERFLOW

__except( GetExceptionCode() == STATUS_INTEGER_OVERFLOW ? 1 : 0 )  

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

__except( GetExceptionCode() == STATUS_INTEGER_OVERFLOW )  

Условный оператор более полезен в ситуациях, когда фильтр может иметь значение -1. EXCEPTION_CONTINUE_EXECUTION

Оператор запятых позволяет выполнять несколько выражений в последовательности. Затем он возвращает значение последнего выражения. Например, в следующем коде код исключения сохраняется в переменной и затем проверяется.

__except( nCode = GetExceptionCode(), nCode == STATUS_INTEGER_OVERFLOW ) 

Неожиданное поведение фильтров исключений

Искал информацию по фильтрам исключений в C# и наткнулся на эту статью. В статье описывается случай, когда происходит исключение непосредственно внутри фильтра исключений, например:

class Program < public static void Main() < TestExceptionFilters(); >public static void TestExceptionFilters() < try < throw new Exception("Original Exception"); >catch (Exception ex) when (MyCondition()) < Console.WriteLine(ex); >> public static bool MyCondition() < throw new Exception("Condition Exception"); >> 

Со следующим комментарием к коду:

Таким образом, мы можем указать условие в фильтре исключений; catch-блок будет исполняться только в том случае, если условие выполнилось. И мы можем использовать bool-функцию в качестве условия. Но что произойдёт, если само условие выбросит исключение? Ожидаемым поведением является следующее: исключение игнорируется, условия считается ложным

Вот уж для меня действительно стало неожиданным, что ожидаемое поведение - это игнорирование исключения. Сразу не поверил, но компилятор ведет себя так, как и ожидают люди: игнорирует исключение в фильтре Сам вопрос: как так произошло, что игнорировать исключения - это норма? А что если я захочу логгировать ошибку в фильтре (это одно из применений фильтров исключений), а у меня там будет ошибка (нет такого файла, например), то мало того, что я не увижу, что у меня есть ошибка, так еще и не зайду в нужный блок catch , так как "условие будет считаться ложным"

Отслеживать
задан 21 апр 2017 в 15:01
1,214 1 1 золотой знак 8 8 серебряных знаков 16 16 бронзовых знаков
А разве условие не может быть методом внутри которого try. catch и логировать?
21 апр 2017 в 15:06

1) Как вы себе представляете обработку исключения в фильтре? 2) Фильтры предназначены в первую очередь для простейших проверок in-place и если проверка может выбросить исключение, возможно, вы делаете что-то не так.

21 апр 2017 в 15:49

@UladzimirPalekh, не совсем разумно игнорировать. Сейчас объясню: я (или не я) писал функцию для фильтра и не обернул ее в try (устал/забыл/захотел ловить в другом месте/вообще не я писал, просто помогаю). Затем происходит ошибка приложение, валиться и я не могу понять в чем именно ошибка! Потому что то исключение проигнорировалось, в нужный catch я не зашел. Поиск ошибки превратился в ад

21 апр 2017 в 15:57

1) Предположим, исключение из MyConditon() не подавляется. Как и где оно должно обрабатываться вне этого метода? И что с оригинальным исключением? Напишите C# псевдокод, как бы вы хотели обрабатывать такое исключение (язык он ведь не только о том, логично что-то или нелогично, но и о том, как это будет выглядеть в тексте). 2) Фильтр придуман -- сюрприз -- именно для фильтрации (то, что у Теплякова идет сценарием под номером два). Все остальное -- побочные эффекты, которые можно использовать как лазейки.

21 апр 2017 в 16:10

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

21 апр 2017 в 16:14

1 ответ 1

Сортировка: Сброс на вариант по умолчанию

Очень похоже что это не норма, а банальная необходимость. И вот почему: в .NET и C# в частности гарантируется выполнение блока finally , независимо от результатов работы кода в блоке try и наличия либо отсутствия обработки возникших исключений в блоках catch . Ваш код порождает исключение в потоке управления конструкции try . catch . finally . Если бы оно не было проигнорировано, это привело бы к прерыванию потока управления и невозможности выполнить блок finally . Но выполнение finally гарантировано спецификацией.

Получили явное противоречие. Простой выход из ситуации - игнорировать исключения в потоке управления конструкции try . catch . finally , для соблюдения спецификации.

Можно было бы пробросить полученное исключение на уровень выше, а в свойстве InnerException передать исходное исключение, из-за которого все и произошло, но от такого решения будет больше путаницы, чем пользы.

PS: упомянутый поток управления не имеет ни какого отношения к многопоточности

Непосредственно к вопросу не относится, но для общей картины нужно добавить, что есть ряд исключений, для которых не выполняются ни catch , ни finally . Как правило это исключения вызывающие падение домена приложения или самой CLR, причина которых может быть как серьезной внутренней ошибкой приложения (например StackOverflowException), так и внешними проблемами CLR или операционной системы (частный случай OutOf MemoryException).

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *