
ASP_NET_MVC_4_Framework_s_primerami_na_C_dlya_p
.pdf
Как избегать лишних исключений
Преимущества использования представления для отображения ошибки заключаются в том, что вы можете использовать макеты, чтобы сообщение об ошибке выглядело согласовано со всем приложением, и генерировать динамический контент, который поможет пользователю понять, что произошло не так и что можно исправить.
Недостатком этого подхода является то, что вам придется тщательно протестировать представление и убедиться, что вы не просто генерируете еще одно исключение. Мы часто с этим сталкиваемся, когда разработчики уделяют основное внимание тестированию основных функций приложения и не учитывают всех потенциальных ситуаций, которые могут привести к ошибке. Чтобы продемонстрировать это, мы добавили блок кода 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: Исключение, которое возникает при попытке визуализировать представление
401

Это нереалистичный сценарий, но он показывает, что произойдет, если у нас будут проблемы с представлением: пользователь увидит странную ошибку, которая даже не относится к его проблеме с приложением. Если вы используете представление для фильтра исключений, тщательно протестируйте это представление.
Используем встроенный фильтр исключений
Мы разобрали процесс создания фильтров исключений, потому что важно понимать, что происходит «под капотом» в 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>
402
</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>
403

<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, в котором свойству CSS display задали значение none, благодаря чему он стал невидимым для
пользователя.
Использование фильтров для методов действий
Фильтры действий можно использовать для различных целей. Встроенный класс для создания таких фильтров, IActionFilter, показан в листинге 16-21.
Листинг 16-21: Интерфейс IActionFilter
namespace System.Web.Mvc
{
public interface IActionFilter
{
void OnActionExecuting(ActionExecutingContext filterContext); void OnActionExecuted(ActionExecutedContext filterContext);
}
}
Этот интерфейс определяет два метода. MVC Framework вызывает метод OnActionExecuting перед тем, как вызвать метод действия, и OnActionExecuted после вызова метода действия.
Реализуем метод OnActionExecuting
Метод OnActionExecuting вызывается до метода действия. Вы можете использовать его для того, чтобы изучить запрос и принять решение о его отмене, изменении или задержке его выполнения. Параметром этого метода является объект ActionExecutingContext, который создает подкласс класса ControllerContext и определяет два дополнительных свойства, описанных в таблице 16-7.
404

Таблица 16-7: Свойства ActionExecutingContext
Название |
Тип |
Описание |
|
||
ActionDescriptor |
ActionDescriptor |
Предоставляет информацию о методе действия |
|
|
|
Result |
ActionResult |
Результат для метода действия; фильтр может отменить |
запрос, установив данному свойству иное значение, кроме |
||
|
|
null |
Вы можете использовать фильтр, чтобы отменить запрос, определив в качестве значения для свойства Result результат действия. Чтобы это продемонстрировать, мы создали свой собственный класс фильтра действий под названием CustomActionAttribute в папке Infrastructure, как показано в листинге 16-22.
Листинг 16-22: Отмена запроса в методе OnActionExecuting
using System.Web.Mvc;
namespace Filters.Infrastructure
{
public class CustomActionAttribute : FilterAttribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.HttpContext.Request.IsLocal)
{
filterContext.Result = new HttpNotFoundResult();
}
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
// not yet implemented
}
}
}
В этом примере мы используем метод OnActionExecuting, чтобы проверить, отправлен ли запрос с локальной машины. Если это так, то возвращаем пользователю ответ 404 — Not Found.
Примечание
Как вы видите из листинга 16-22, необязательно реализовывать оба метода, определенные в интерфейсе IActionFilter, чтобы создать рабочий фильтр. Будьте осторожны, чтобы не вызвать NotImplementedException, которое Visual Studio добавляет к классу при реализации
данного интерфейса. В фильтре действий MVC Framework вызывает оба метода, и если возникает исключение, то будут инициированы фильтры исключений. Если вам не нужно добавлять к методу какую-либо логику, просто оставьте его пустым.
Фильтр действий применяется так же, как и любой другой атрибут. Чтобы продемонстрировать фильтр, который мы создали в листинге 16-22, мы добавили новый метод действия в контроллер Home, как показано в листинге 16-23.
Листинг 16-23: Добавляем новое действие в контроллер Home
[CustomAction]
public string FilterTest()
{
return "This is the FilterTest action";
}
405

Вы можете протестировать фильтр, запустив приложение и перейдя по ссылке /Home/FilterTest. Если вы отправили запрос с локальной машины, то вы увидите результат, показанный на рисунке 16- 7, хотя, как мы знаем, и контроллер, и действие существуют.
Рисунок 16-7: Эффект от использования фильтра действий
Реализуем метод OnActionExecuted
Фильтр также можно использовать для выполнения каких-либо задач, которые измеряют время выполнения метода действия. В качестве простого примера мы создали новый класс ProfileActionAttribute в папке Infrastructure, который измеряет количество времени, которое занимает выполнение метода действия. Код этого фильтра показан в листинге 16-24.
Листинг 16-24: Более сложный фильтр действий
using System.Diagnostics; using System.Web.Mvc;
namespace Filters.Infrastructure
{
public class ProfileActionAttribute : FilterAttribute, IActionFilter
{
private Stopwatch timer;
public void OnActionExecuting(ActionExecutingContext filterContext)
{
timer = Stopwatch.StartNew();
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
timer.Stop();
if (filterContext.Exception == null)
{
filterContext.HttpContext.Response.Write( string.Format("<div>Action method elapsed time: {0}</div>",
timer.Elapsed.TotalSeconds));
}
}
}
}
В этом примере мы запускаем таймер с помощью метода OnActionExecuting (используется класс таймера Stopwatch из пространства имен System.Diagnostics). Метод OnActionExecuted
вызывается по завершении метода действия. В листинге 16-25 показано, как мы применили атрибут к контроллеру Home (ранее созданный фильтр был удален, чтобы локальные запросы не перенаправлялись).
406

Листинг 16-25: Применяем фильтр действий к контроллеру Home
[ProfileAction]
public string FilterTest()
{
return "This is the ActionFilterTest action";
}
Если вы запустите приложение и перейдете по ссылке /Home/FilterTest, то увидите те же результаты, что и на рисунке 16-8.
Рисунок 16-8: Используем фильтр действий для измерения производительности
Подсказка
Обратите внимание, что информация о производительности отображается в браузере перед результатом метода действия. Так происходит потому, что фильтр действий выполняется после завершения метода действия, но до обработки результата.
В качестве параметра в метод OnActionExecuted передается объект ActionExecutedContext. Этот класс определяет дополнительные свойства, которые показаны в таблице 16-8. Свойство Exception возвращает любое исключение, выброшенное методом действия, а свойство ExceptionHandled указывает, было ли оно обработано другим фильтром.
Таблица 16-8: Свойства ActionExecutedContext
Название |
Тип |
Описание |
ActionDescriptorActionDescriptor
Предоставляет информацию о методе действия
Canceled bool
Exception Exception
Возвращает true, если действие было отменено другим фильтром
Возвращает исключение, выброшенное другим фильтром или методом действия
ExceptionHandled |
bool |
Возвращает |
, если исключение было обработано |
|
|
|
true |
|
|
|
|
Result |
ActionResult |
Результат для метода действия; фильтр может отменить |
|
запрос, установив для этого свойства иное значение, кроме |
|||
|
|
null |
|
Свойство Canceled возвращает true, если запрос был отменен другим фильтром (т.е. он установил значение для свойства Result) с того момента, когда был запущен метод фильтра OnActionExecuting. Метод OnActionExecuted все равно будет вызван, но только для того, чтобы можно было освободить или очистить ранее используемые ресурсы.
407
Использование фильтров для результата
Фильтры результатов – это фильтры общего назначения, которые обрабатывают результаты, произведенные методами действий. Фильтры результатов реализуют интерфейс IResultFilter, который показан в листинге 16-26.
Листинг 16-26: Интерфейс IResultFilter
namespace System.Web.Mvc
{
public interface IResultFilter
{
void OnResultExecuting(ResultExecutingContext filterContext); void OnResultExecuted(ResultExecutedContext filterContext);
}
}
Как мы узнали в главе 15, методы действий возвращают результаты действий, что позволяет нам отделять цель метода действия от его исполнения. Применение фильтра результатов к методу действия означает, что метод OnResultExecuting будет вызван после того, как метод действия вернет результат, но до того, как результат будет выполнен. Метод OnResultExecuted будет вызван после выполнения результата действия.
Параметрами для этих методов являются соответственно объекты ResultExecutingContext и ResultExecutedContext, и они очень похожи на аналогичные фильтры методов действий. Они определяют те же свойства, которые обладают теми же эффектами (см. таблицу 16-8).
Чтобы продемонстрировать простой фильтр результатов, мы создали новый файл класса под названием ProfileResultAttribute.cs в папке Infrastructure и определили в нем класс,
показанный в листинге 16-27.
Листинг 16-27: Простой фильтр результатов
using System.Diagnostics; using System.Web.Mvc;
namespace Filters.Infrastructure
{
public class ProfileResultAttribute : FilterAttribute, IResultFilter
{
private Stopwatch timer;
public void OnResultExecuting(ResultExecutingContext filterContext)
{
timer = Stopwatch.StartNew();
}
public void OnResultExecuted(ResultExecutedContext filterContext)
{
timer.Stop();
filterContext.HttpContext.Response.Write( string.Format("<div>Result elapsed time: {0}</div>",
timer.Elapsed.TotalSeconds));
}
}
}
Этот фильтр результатов является дополнением к фильтру действий, который мы создали в предыдущем разделе, и измеряет количество времени, необходимого для выполнения результата. В листинге 16-28 вы можете увидеть, как мы применили этот новый фильтр к контроллеру Home.
408

Листинг 16-28: Применяем фильтр результатов к контроллеру Home
[ProfileAction]
[ProfileResult]
public string FilterTest()
{
return "This is the ActionFilterTest action";
}
На рисунке 16-9 показан результат запуска программы и перехода по ссылке /Home/FilterTest. Как видите, оба фильтра добавили данные в ответ браузеру - вывод из фильтра результата показан после результата от метода действия. Естественно, поскольку MVC Framework не может выполнить метод OnResultExecuted до обработки результата, строковое значение будет вставлено в результат.
Рисунок 16-9: Эффект применения фильтра результатов
Встроенный класс фильтра действий и результатов
MVC Framework включает в себя встроенный класс, который можно использовать для создания как фильтров действий, так и фильтров результатов. Этот класс, ActionFilterAttribute, показан в листинге 16-29.
Листинг 16-29: Класс ActionFilterAttribute
public abstract class ActionFilterAttribute : FilterAttribute, IActionFilter, IResultFilter
{
public virtual void OnActionExecuting(ActionExecutingContext filterContext)
{
}
public virtual void OnActionExecuted(ActionExecutedContext filterContext)
{
}
public virtual void OnResultExecuting(ResultExecutingContext filterContext)
{
}
public virtual void OnResultExecuted(ResultExecutedContext filterContext)
{
}
}
Единственное преимущество этого класса заключается в том, что вам не придется переопределять и реализовать методы, которые вы не собираетесь использовать. Никаких других преимуществ реализации интерфейсов фильтров напрямую нет.
409

Чтобы продемонстрировать работу класса ActionFilterAttribute, мы добавили новый файл под названием ProfileAllAttribute.cs в папку Infrastructure и определили в нем класс, показанный в листинге 16-30.
Листинг 16-30: Используем класс ActionFilterAttribute
using System.Diagnostics; using System.Web.Mvc;
namespace Filters.Infrastructure
{
public class ProfileAllAttribute : ActionFilterAttribute
{
private Stopwatch timer;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
timer = Stopwatch.StartNew();
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
timer.Stop();
filterContext.HttpContext.Response.Write( string.Format("<div>Total elapsed time: {0}</div>",
timer.Elapsed.TotalSeconds));
}
}
}
Класс ActionFilterAttribute реализует интерфейсы IActionFilter и IResultFilter, что означает,
что MVC Framework будет считать производные классы фильтрами обоих типов, даже если не все методы переопределены. В нашем примере мы реализовали только метод OnActionExecuting из интерфейса IActionFilter и OnResultExecuted из интерфейса IResultFilter, что позволит нам продолжить анализ производительности и измерить количество времени, необходимое для выполнения метода действия и обработки результата, в едином блоке. В листинге 16-31 показано применение фильтра к контроллеру Home.
Листинг 16-31: Применяем фильтр к контроллеру Home
[ProfileAction]
[ProfileResult]
[ProfileAll]
public string FilterTest()
{
return "This is the FilterTest action";
}
Вы можете увидеть эффект от всех этих фильтров, если запустите приложение и перейдите по ссылке /Home/FilterTest. Результат показан на рисунке 16-10.
Рисунок 16-10: Эффект добавления фильтра ProfileAll
410