Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

ASP_NET_MVC_4_Framework_s_primerami_na_C_dlya_p

.pdf
Скачиваний:
29
Добавлен:
19.03.2016
Размер:
17.66 Mб
Скачать

Как избегать лишних исключений

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

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

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]