Использование фильтров для исключений
Если при вызове метода действия было выброшено необработанное исключение, будет запущен фильтр исключений. Исключения могут поступать из:
- другого фильтра (фильтра авторизации, действия или результата);
- самого метода действия;
- при выполнении результата действия (подробная информация о результатах действия дана в главе 15).
Создаем фильтр исключения
Фильтры исключений должны реализовывать интерфейс IExceptionFilter
, который показан в листинге 16-10.
Листинг 16-10: Интерфейс IExceptionFilter
namespace System.Web.Mvc
{
public interface IExceptionFilter
{
void OnException(ExceptionContext filterContext);
}
}
Когда появится необработанное исключение, будет вызван метод OnException
. Параметром для этого метода является объект ExceptionContext
, который наследует от ControllerContext
и имеет ряд полезных свойств, с помощью которых можно получить информацию о запросе. Они приведены в таблице 16-3.
Таблица 16-3: Свойства ControllerContext
Название | Тип | Описание |
Controller |
ControllerBase |
Возвращает объект контроллера для данного запроса |
HttpContext |
HttpContextBase |
Обеспечивает доступ к информации о запросе и доступ к ответу |
IsChildAction |
bool |
Возвращает true , если это дочернее действие (будет кратко обсуждаться позже в этой главе и подробно в главе 18) |
RequestContext |
RequestContext |
Предоставляет доступ к объекту HttpContext и данным маршрутизации, хотя и то, и то доступно через другие свойства |
RouteData |
RouteData |
Возвращает данные маршрутизации для данного запроса |
В дополнение к свойствам, наследованным от класса ControllerContext
, класс ExceptionContext
определяет некоторые дополнительные свойства, которые также полезны при работе с исключениями. Они показаны в таблице 16-4.
Подсказка
Мы разделили эти таблицы, потому что есть и другие классы контекста фильтров, которые наследуют от
ControllerContext
и которые мы представим по ходу этой главы.
Таблица 16-4: Дополнительные свойства ExceptionContext
Название | Тип | Описание |
ActionDescriptor |
ActionDescriptor |
Предоставляет подробную информацию о методе действия |
Result |
ActionResult |
Результат для метода действия; фильтр может отменить запрос, установив для этого свойства иное значение, кроме null
|
Exception |
Exception |
Необработанное исключение |
ExceptionHandled |
bool |
Возвращает true , если другой фильтр отметил это исключение как обработанное
|
Сгенерированное исключение доступно через свойство Exception
. Фильтр исключения может сообщить, что он обработал исключение, установив для свойства ExceptionHandled
значение true
. Вызываются все фильтры исключений, примененные к действию, даже если ExceptionHandled
уже содержит true
, так что лучше всегда проверять, обработано ли исключение другим фильтром, чтобы не решать повторно уже решенную проблему.
Примечание
Если ни один из фильтров исключений не установил свойству
ExceptionHandled
значениеtrue
, MVC Framework будет использовать стандартную процедуру обработки исключений ASP.NET, которая приведет к самому нежелательному результату - "желтому экрану смерти".
Свойство Result
используется фильтром исключений, чтобы сообщить MVC Framework дальнейшую последовательность действий. В основном это регистрация исключения и отображение соответствующего сообщения для пользователя. Чтобы продемонстрировать выполнение этих задач, мы создали новый класс под названием RangeExceptionAttribute.cs
, который добавили в папку Infrastructure
нашего проекта. Содержимое этого файла показано в листинге 16-11.
Листинг 16-11: Реализуем фильтр исключений
using System;
using System.Web.Mvc;
namespace Filters.Infrastructure
{
public class RangeExceptionAttribute : FilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
if (!filterContext.ExceptionHandled &&
filterContext.Exception is ArgumentOutOfRangeException)
{
filterContext.Result
= new RedirectResult("~/Content/RangeErrorPage.html");
filterContext.ExceptionHandled = true;
}
}
}
}
Этот фильтр исключений обрабатывает экземпляры ArgumentOutOfRangeException
, перенаправляя браузер пользователя к файлу RangeErrorPage.html
из папки Content
.
Обратите внимание, что мы наследовали класс RangeExceptionAttribute
от класса FilterAttribute
наряду с реализацией интерфейса IExceptionFilter
. Чтобы класс атрибута .NET работал как фильтр MVC, класс должен реализовать интерфейс IMvcFilter
. Это можно сделать напрямую, но самый простой способ создать фильтр – это наследовать от класса FilterAttribute
, который реализует необходимый интерфейс и предоставляет некоторые полезные базовые функции, такие как изменение стандартного порядка выполнения фильтров (к чему мы вернемся позже в этой главе).
Применяем фильтр исключений
Прежде чем мы сможем протестировать наш фильтр исключений, нужно создать для этого некоторую базу. Во-первых, создадим в проекте папку Content
и в ней - файл RangeErrorPage.html
. Он будет использоваться для отображения простого сообщения, которые вы можете видеть в листинге 16-12.
Листинг 16-12: Содержимое файла RangeErrorPage.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Range Error</title>
</head>
<body>
<h2>Sorry</h2>
<span>One of the arguments was out of the expected range.</span>
</body>
</html>
Далее нам нужно добавить метод действия в контроллер Home
, который будет выбрасывать интересующее нас исключение. Он показан в листинге 16-13.
Листинг 16-13: Добавляем новое действие в контроллер Home
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Filters.Infrastructure;
namespace Filters.Controllers
{
public class HomeController : Controller
{
[Authorize(Users = "adam, steve, jacqui", Roles = "admin")]
public string Index()
{
return "This is the Index action on the Home controller";
}
public string RangeTest(int id)
{
if (id > 100)
{
return String.Format("The id value is: {0}", id);
}
else
{
throw new ArgumentOutOfRangeException("id", id, "");
}
}
}
}
Если вы запустите приложение и перейдете по ссылке /Home/RangeTest/50
, то увидите стандартную обработку исключения. В маршрутизации по умолчанию, которую для проекта создает Visual Studio, есть переменная сегмента под названием id
, которой в этом URL мы установили значение 50
, что и приведет к результату, показанному на рисунке 16-3. (Подробно маршрутизация и сегменты URL описаны в главах 13 и 14.)
Рисунок 16-3: Ответ стандартной обработки исключений

Мы можем применить фильтр исключений либо к контроллерам, либо к отдельным действиям, как показано в листинге 16-14.
Листинг 16-14: Применяем фильтр
[RangeException]
public string RangeTest(int id)
{
if (id > 100)
{
return String.Format("The id value is: {0}", id);
}
else
{
throw new ArgumentOutOfRangeException("id");
}
}
Если вы перезапустите приложение и снова перейдете по ссылке Home/RangeTest/50
, то увидите результат, показанный на рисунке 16-4.
Рисунок 16-4: Эффект применения фильтра исключений

Используем представление для вывода ответа при выбросе исключения
Проще и безопаснее всего для отображения ответа при выбросе исключения использовать страницу или статический контент – едва ли при таком подходе что-то пойдет не так и возникнут дополнительные проблемы. Однако такое решение будет не достаточно хорошим для пользователя, который получит общее предупреждение и будет выброшен из приложения.
Альтернативный подход - использовать представление для отображения конкретного сообщения о проблеме и предоставления пользователю некоторой контекстной информации и возможностей, с помощью которых можно данную проблему решить. Чтобы это продемонстрировать, мы внесли некоторые изменения в класс RangeExceptionAttribute
, как показано в листинге 16-15.
Листинг 16-15: Возвращаем представление из фильтра исключений
using System;
using System.Web.Mvc;
namespace Filters.Infrastructure
{
public class RangeExceptionAttribute : FilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
if (!filterContext.ExceptionHandled &&
filterContext.Exception is ArgumentOutOfRangeException)
{
int val = (int)(((ArgumentOutOfRangeException) filterContext.Exception).ActualValue);
filterContext.Result = new ViewResult
{
ViewName = "RangeError",
ViewData = new ViewDataDictionary<int>(val)
};
filterContext.ExceptionHandled = true;
}
}
}
}
Мы создали объект ViewResult
и установили значения его свойств ViewName
и ViewData
, определяющие название представления и объект модели, который будет в него передан. Это немного запутанный код, потому что мы работаем с объектом ViewResult
напрямую и не используем определенный в классе Controller
метод View
, который всегда применяем в методах действий. Мы не станем останавливаться на этом коде, потому что представления будут подробно рассмотрены в главе 18, и встроенный фильтр исключений, который мы опишем в следующем разделе, позволит достичь того же эффекта более понятным способом. Сейчас мы просто хотим вам показать, как все работает «под капотом».
В объекте ViewResult
мы указываем представление под названием RangeError
и передаем значение int
вызвавшего исключение аргумента в качестве объекта модели представления. Затем мы добавим папку Views/Shared
в проект Visual Studio и создадим в нем файл RangeError.cshtml
, содержимое которого показано в листинге 16-16.
Листинг 16-16: Файл представления RangeError.cshtml
@model int
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Range Error</title>
</head>
<body>
<h2>Sorry</h2>
<span>The value @Model was out of the expected range.</span>
<div>
@Html.ActionLink("Change value and try again", "Index")
</div>
</body>
</html>
В файле представления мы используем стандартные теги HTML и Razor, чтобы предоставить пользователю (немного) более полезное сообщение. Наш пример приложения довольно ограничен, поэтому у нас нет каких-либо полезных страниц, на которые можно направить пользователя для решения этой проблемы. Мы создали ссылки на другой метод действия с помощью вспомогательного метода ActionLink
, просто чтобы показать, что в представлении вы можете использовать какие угодно функции. Чтобы увидеть результат, перезапустите приложение и перейдите по ссылке /Home/RangeTest/50
, как показано на рисунке 16-5.
Рисунок 16-5: Используем представление для отображения сообщения от фильтра исключения

Как избегать лишних исключений
Преимущества использования представления для отображения ошибки заключаются в том, что вы можете использовать макеты, чтобы сообщение об ошибке выглядело согласовано со всем приложением, и генерировать динамический контент, который поможет пользователю понять, что произошло не так и что можно исправить.
Недостатком этого подхода является то, что вам придется тщательно протестировать представление и убедиться, что вы не просто генерируете еще одно исключение. Мы часто с этим сталкиваемся, когда разработчики уделяют основное внимание тестированию основных функций приложения и не учитывают всех потенциальных ситуаций, которые могут привести к ошибке. Чтобы продемонстрировать это, мы добавили блок кода Razor в представление RangeError.cshtml
, который вызовет исключение, как показано в листинге 16-17.
Листинг 16-17: Добавляем в представление код, который вызовет исключение
@model int
@{
var count = 0;
var number = Model / count;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Range Error</title>
</head>
<body>
<h2>Sorry</h2>
<span>The value @Model was out of the expected range.</span>
<div>
@Html.ActionLink("Change value and try again", "Index")
</div>
</body>
</html>
При визуализации представления наш код будет генерировать DivideByZeroException
. Если вы запустите приложение и снова перейдите по ссылке /Home/RangeTest/50
, то увидите исключение, которое возникает при попытке визуализировать представление, а не то, которое выбрасывает контроллер, как показано на рисунке 16-6.
Рисунок 16-6: Исключение, которое возникает при попытке визуализировать представление

Это нереалистичный сценарий, но он показывает, что произойдет, если у нас будут проблемы с представлением: пользователь увидит странную ошибку, которая даже не относится к его проблеме с приложением. Если вы используете представление для фильтра исключений, тщательно протестируйте это представление.
Используем встроенный фильтр исключений
Мы разобрали процесс создания фильтров исключений, потому что важно понимать, что происходит «под капотом» в MVC Framework. Но в реальных проектах вам не часто понадобится создавать пользовательские фильтры, потому что Microsoft включила в MVC Framework атрибут HandleErrorAttribute
, который является встроенной реализацией интерфейса IExceptionFilter
. Вы можете указать исключения, имена представлений и разметку, используя свойства, описанные в таблице 16-5.
Таблица 16-5: Свойства HandleErrorAttribute
Название | Тип | Описание |
ExceptionType |
Type |
Тип исключения, который обрабатываться данным фильтром. Это свойство также будет обрабатывать типы исключений, которые наследуют от указанного, но будет игнорировать все другие. По умолчанию для ExceptionType указано значение System.Exception , что означает, что оно будет обрабатывать все стандартные исключения.
|
View |
string |
Название шаблона представления, которое визуализируется данным фильтром. Если вы не указываете значение, по умолчанию устанавливается значение Error , так что будет визуализировано Views/<currentControllerName>/Error.cshtml или /Views/Shared/Error.cshtml .
|
Master |
string |
Имя макета, который используется при визуализации представления данного фильтра. Если вы не указываете значение, представление использует свой макет страницы по умолчанию. |
Когда появляется необработанное исключение указанного типа в ExceptionType
, HandleErrorAttribute
визуализирует представление, указанное в свойстве View
(используя макет по умолчанию или определенный в свойстве Master
).
Готовимся использовать встроенный фильтр исключений
Фильтр HandleErrorAttribute
работает только тогда, когда в файле Web.config
включена обработка пользовательских исключений. Поэтому мы добавляем атрибут customErrors
в узел <system.web>
, как показано в листинге 16-18.
Листинг 16-18: Включаем обработку пользовательских исключений в файле Web.config
<system.web>
<httpRuntime targetFramework="4.5" />
<compilation debug="true" targetFramework="4.5" />
<pages>
<namespaces>
<add namespace="System.Web.Helpers" />
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
<add namespace="System.Web.WebPages" />
</namespaces>
</pages>
<customErrors mode="On" defaultRedirect="/Content/RangeErrorPage.html"/>
</system.web>
По умолчанию для атрибута mode
установлено значение RemoteOnly
, что означает, что во время разработки HandleErrorAttribute
не будет перехватывать исключения, но когда вы развернете приложение на сервере и начнете делать запросы с другого компьютера, то HandleErrorAttribute
вступит в силу. Чтобы понять, что увидят конечные пользователи, установите для customErrors
mode
значение On
. В атрибуте defaultRedirect
указывается страница по умолчанию, которая будет отображаться, если все остальные не работают.
Применяем встроенный фильтр исключений
В листинге 16-19 показано, как мы применили фильтр HandleError
к контроллеру Home
.
Листинг 16-19: Используем фильтр HandleErrorAttribute
[HandleError(ExceptionType = typeof(ArgumentOutOfRangeException), View = "RangeError")]
public string RangeTest(int id)
{
if (id > 100)
{
return String.Format("The id value is: {0}", id);
}
else
{
throw new ArgumentOutOfRangeException("id", id, "");
}
}
В этом примере мы создали ту же ситуацию, которая у нас была с пользовательским фильтром, то есть при возникновении ArgumentOutOfRangeException
пользователь увидит представление RangeError
.
Визуализируя представление, фильтр HandleErrorAttribute
передает объект модели представления HandleErrorInfo
, который содержит исключение и дополнительную информацию, которую мы будем использовать в представлении. В таблице 16-6 приведены свойства, определенные в классе HandleErrorInfo
.
Таблица 16-6: Свойства HandleErrorInfo
Название | Тип | Описание |
ActionName |
string |
Возвращает имя метода действия, который сгенерировал исключение |
ControllerName |
string |
Возвращает имя контроллера, который сгенерировал исключение |
Exception |
Exception |
Возвращает исключение |
Обратите внимание, как мы обновили представление RangeError.cshtml
, чтобы использовать этот объект модели в листинге 16-20.
Листинг 16-20: Используем объект моделиHandleErrorInfo
в представленииRangeError
@model HandleErrorInfo
@{
ViewBag.Title = "Sorry, there was a problem!";
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Range Error</title>
</head>
<body>
<h2>Sorry</h2>
<span>The value @(((ArgumentOutOfRangeException)Model.Exception).ActualValue)
was out of the expected range.</span>
<div>
@Html.ActionLink("Change value and try again", "Index")
</div>
<div style="display: none">
@Model.Exception.StackTrace
</div>
</body>
</html>
Мы должны были привести значение свойстваModel.Exception
к типу ArgumentOutOfRangeException
только затем, чтобы потом иметь возможность прочитать свойство ActualValue
, так как класс HandleErrorInfo
является объектом модели общего назначения и используется для передачи любого исключения в представление.
Внимание
При использовании фильтра
HandleError
иногда возникает странное поведение, при котором представление не отображается пользователю, пока в него не включено значение свойстваModel.Exception.StackTrace
. Так как мы не хотим его отображать, мы заключили его вывод в элементdiv
, в котором свойству CSSdisplay
задали значениеnone
, благодаря чему он стал невидимым для пользователя.