
ASP_NET_MVC_4_Framework_s_primerami_na_C_dlya_p
.pdf
сделан браузером для другого URL. GET запросы не должны изменять состояние приложения, так что любая случайная повторная отправка этого запроса не вызовет никаких проблем.
Когда вы выполняете перенаправление, вы отправляете браузеру один из двух HTTP кодов:
HTTP код 302, который является временным перенаправлением. Это наиболее часто используемый тип перенаправления и при использовании паттерна Post/Redirect/Get, это именно тот код, который вы хотите отправить.
HTTP код 301, который указывает на постоянное перенаправление. Его следует использовать с осторожностью, поскольку он говорит получателю HTTP кода никогда снова не запрашивать оригинальный URL, а использовать новый URL, который включен в код перенаправления. Если вы сомневаетесь, используйте временное перенаправление, то есть, отправляйте код 302.
Перенаправление на URL литерального формата
Самый простой способ перенаправить браузер заключается в вызове метод Redirect, который возвращает экземпляр класса RedirectResult, как показано в листинге 15-20.
Листинг 15-20: Перенаправление на URL литерального формата
using System;
using System.Web.Mvc;
namespace ControllersAndActions.Controllers
{
public class ExampleController : Controller
{
public ViewResult Index()
{
ViewBag.Message = "Hello";
ViewBag.Date = DateTime.Now; return View();
}
public RedirectResult Redirect()
{
return Redirect("/Example/Index");
}
}
}
URL, на который вы хотите сделать перенаправление, выражается в виде строки и передается в качестве параметра методу Redirect. Метод Redirect реализует временное перенаправление. Вы можете создать постоянное перенаправление, используя метод RedirectPermanent, как показано в листинге 15-21.
Листинг 15-21: Реализация постоянного перенаправления на литеральный URL
...
public RedirectResult Redirect() {
return RedirectPermanent("/Example/Index");
}
...
Совет
Если вы хотите, вы можете использовать перегруженную версию метода Redirect. Она принимает параметр bool, который определяет, является ли перенаправление
постоянным или нет.
381
Юнит тест: перенаправление на URL литерального формата
Перенаправление на URL литерального формата легко протестировать. Вы можете прочитать URL и проверить, является перенаправление постоянным или временным, используя свойства Url и Permanent класса RedirectResult. Ниже приведен тестовый метод для перенаправления, показанного в листинге 15-21:
...
[TestMethod]
public void RedirectTest() {
//Arrange - create the controller ExampleController target = new ExampleController();
//Act - call the action method
RedirectResult result = target.Redirect(); // Assert - check the result Assert.IsFalse(result.Permanent);
Assert.AreEqual("/Example/Index", result.Url);
}
...
Перенаправление на URL системы маршрутизации
Если вы перенаправляете пользователя в другую часть вашего приложения, вы должны убедиться, что URL, который вы отправляете, действителен внутри вашей URL схемы, как было описано в предыдущей главе. Проблема с использованием URL литерального формата для перенаправления заключается в том, что любые изменения в своей схеме маршрутизации приводят к тому, что вы должны пройти по всему коду и обновить URL.
В качестве альтернативы вы можете использовать систему маршрутизации, чтобы генерировать правильные URL при помощи метода RedirectToRoute, который создает экземпляр
RedirectToRouteResult, как показано в листинге 15-22.
Листинг 15-22: Перенаправление на URL системы маршрутизации
using System;
using System.Web.Mvc;
namespace ControllersAndActions.Controllers
{
public class ExampleController : Controller
{
public ViewResult Index()
{
ViewBag.Message = "Hello";
ViewBag.Date = DateTime.Now; return View();
}
public RedirectToRouteResult Redirect()
{
return RedirectToRoute(new
{
controller = "Example", action = "Index",
ID = "MyID" });
}
}
}
Метод RedirectToRoute работает с временным перенаправлением. Используйте метод RedirectToRoutePermanent для постоянного перенаправления. Оба метода принимают анонимный
382

тип, чьи свойства затем передаются системе маршрутизации для генерации URL. Более подробную информацию об этом процессе см. в главе 14.
Юнит тестирование: роутовые перенаправления
Вот юнит тест, который мы добавили в файл ActionTests.cs. Он тестирует метод действия из листинга 15-22:
...
[TestMethod]
public void RedirectValueTest() {
//Arrange - create the controller ExampleController target = new ExampleController();
//Act - call the action method
RedirectToRouteResult result = target.RedirectToRoute(); // Assert - check the result Assert.IsFalse(result.Permanent);
Assert.AreEqual("Example", result.RouteValues["controller"]); Assert.AreEqual("Index", result.RouteValues["action"]); Assert.AreEqual("MyID", result.RouteValues["ID"]);
}
...
Перенаправление на метод действия
Вы можете сделать перенаправление на метод действия более изящно, используя метод RedirectToAction. Это всего лишь обертка вокруг метода RedirectToRoute, который позволяет указать значения для метода действия и контроллера без необходимости создания анонимного типа, как показано в листинге 15-23.
Листинг 15-23: Перенаправление при помощи RedirectToAction
...
public RedirectToRouteResult RedirectToRoute() {
return RedirectToAction("Index");
}
...
Если вы просто указываете метод действия, то предполагается, что вы имеете в виду метод действия в текущем контроллере. Если вы хотите сделать перенаправление на другой контроллер, необходимо указать имя в качестве параметра:
...
public RedirectToRouteResult Redirect() {
return RedirectToAction("Index", "Basic");
}
...
Есть и другие перегруженные версии, которые можно использовать для добавления дополнительных значений при создании URL. Они выражаются при помощи анонимного типа, который имеет тенденцию скрывать цели метода, но все же может сделать ваш код более удобным для чтения.
Примечание
Значения, которые вы передаете методу действия и контроллеру, не проверяются, прежде чем передаются системе маршрутизации. Вы несете ответственность за то, чтобы цели, которые вы определяете, действительно существовали.
383
Метод RedirectToAction выполняет временное перенаправление. Для постоянного перенаправления используйте RedirectToActionPermanent.
Сохранение данных во время перенаправления
Перенаправление заставляет браузер отправить совершенно новый HTTP запрос, а это означает, что у вас нет доступа к данным оригинального запроса. Если вы хотите передать данные одного запроса другому, вы можете использовать TempData.
Данные TempData похожи на данные Session, за исключением того, что значения TempData помечаются на удаление, после того как они прочитаны, и они удаляются, когда запрос был обработан. Это идеальное средство для сохранения краткосрочных данных, которые необходимо оставить во время выполнения перенаправления. Вот простой пример в методе действия, который использует метод RedirectToAction:
...
public RedirectToRouteResult RedirectToRoute() { TempData["Message"] = "Hello"; TempData["Date"] = DateTime.Now;
return RedirectToAction("Index");
}
...
Когда этот метод обрабатывает запрос, он устанавливает значения в коллекции TempData, а затем перенаправляет браузер пользователя на метод действия Index в том же контроллере. Вы можете прочитать значения TempData обратно в целевом методе действия, а затем передать их в представление:
...
public ViewResult Index() { ViewBag.Message = TempData["Message"]; ViewBag.Date = TempData["Date"]; return View();
}
...
Более прямым подходом является прочтение этих значений в представлении:
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
The day is: @(((DateTime)TempData["Date"]).DayOfWeek) <p />
The message is: @TempData["Message"]
Прочтение этих значений в представлении означает, что вам не нужно использовать ViewBag в методе действия. Тем не менее, необходимо привести результаты TempData к соответствующему типу.
Вы можете получить значения из TempData без маркировки их на удаление с помощью метода Peek:
DateTime time = (DateTime)TempData.Peek("Date");
Можно сохранить значения, которое в противном случае были бы удалены, с помощью метода Keep:
TempData.Keep("Date");
384
Метод Keep не защищает значение навсегда. Если значение будет прочтено еще раз, оно будет помечено для удаления еще раз. Если вы хотите сохранить данные, чтобы они не были удалены после обработки запроса, используйте данные Session.
Возвращение ошибок и HTTP кодов
Последний из встроенных классов ActionResult, которые мы рассмотрим, может быть использован для отправки клиенту сообщений об ошибках и результирующих HTTP кодов. Большинство приложений не требуют этой функции, потому что MVC автоматически генерирует такие виды результатов. Однако они могут быть полезны, если вам нужен более прямой контроль над ответами, которые отправляются клиенту.
Отправка конкретного результирующего HTTP кода
Вы можете отправить конкретный код HTTP статуса браузеру с помощью класса HttpStatusCodeResult. Для этого не существует вспомогательного метода контроллера, так что вы должны создать экземпляр класса напрямую, как показано в листинге 15-24.
Листинг 15-24: Отправка конкретного кода статуса
using System;
using System.Web.Mvc;
namespace ControllersAndActions.Controllers
{
public class ExampleController : Controller
{
public ViewResult Index()
{
ViewBag.Message = "Hello";
ViewBag.Date = DateTime.Now; return View();
}
public RedirectToRouteResult Redirect()
{
return RedirectToAction("Index", "Basic");
}
public HttpStatusCodeResult StatusCode()
{
return new HttpStatusCodeResult(404, "URL cannot be serviced");
}
}
}
Параметры конструктора для HttpStatusCodeResult являются числовым кодом статуса и дополнительным описательным сообщением. В листинге мы вернули код 404, что означает, что запрашиваемый ресурс не существует.
Отправка результата 404
Мы можем достичь того же результата, что показан в листинге 15-24, с помощью более удобного класса HttpNotFoundResult, который является производным от HttpStatusCodeResult и может быть создан с помощью метода контроллера HttpNotFound, как показано в листинге 15-25.
Листинг 15-25: Создание результата 404
...
public HttpStatusCodeResult StatusCode() { return HttpNotFound();
}
385
...
Отправка результата 401
Другим классом-«оберткой» для конкретного кода HTTP статуса является HttpUnauthorizedResult, который возвращает код 401, используемый для указания того, что запрос является неавторизированным. В листинге 15-26 показан пример.
Листинг 15-26: Генерирование результата 401
...
public HttpStatusCodeResult StatusCode() {
return new HttpUnauthorizedResult();
}
...
В классе Controller нет вспомогательных методов для создания экземпляров HttpUnauthorizedResult, так что вы должны делать это напрямую. Результат возвращения экземпляра этого класса, как правило, заключается в перенаправлении пользователя на страницу аутентификации, как вы видели в главе 11.
Юнит тест: коды HTTP статуса
Класс HttpStatusCodeResult следует тому паттерну, который используется и для других типов результата, и его статус доступен через набор свойств. В данном случае свойство StatusCode возвращает числовой код HTTP статуса, а свойство StatusDescription возвращает соответствующее описание. Следующий тестовый метод предназначен для метода действия из листинга 15-26:
...
[TestMethod]
public void StatusCodeResultTest() {
//Arrange - create the controller ExampleController target = new ExampleController();
//Act - call the action method
HttpStatusCodeResult result = target.StatusCode(); // Assert - check the result
Assert.AreEqual(401, result.StatusCode);
}
...
Резюме
Контроллеры являются одним из ключевых элементов MVC паттерна. В этой главе вы узнали, как создавать "сырые" контроллеры путем реализации интерфейса IController и более удобные контроллеры путем наследования от класса Controller. Вы увидели ту роль, которую играют методы действия в контроллерах MVC фреймворка, и то, как они облегчают модульное тестирование. Мы показали вам различные способы, которыми вы можете получать входные данные и формировать выходные данные от методов действий, и продемонстрировали различные виды ActionResult, которые делают этот процесс простым и гибким.
В следующей главе мы глубже рассмотрим контроллеры, чтобы показать вам фильтры. Они меняют порядок, в котором обрабатываются запросы.
386
Фильтры
Фильтры внедряют дополнительную логику в процесс обработки запросов MVC Framework. Они обеспечивают простой и эффективный механизм для реализации сквозной функциональности (crosscutting concerns). Под этим термином подразумевается функциональность, которая используется везде в приложении и в то же время не может быть заключена в какой-либо один из его модулей, так как это нарушит принцип разделения ответственности (separation of concerns). Классическими примерами сквозной функциональности являются вход в систему, авторизация и кэширование. В этой главе мы рассмотрим различные категории фильтров, которые поддерживает MVC Framework, научимся их создавать, использовать и контролировать их исполнение.
Использование фильтров
Вы уже видели пример фильтра в главе 11, когда мы применили авторизацию к контроллеру администрирования SportsStore. Мы хотели, чтобы данный метод действия использовался только прошедшими аутентификацию пользователями, и это можно было бы реализовать несколькими способами. Например, можно проверять статус авторизированности запроса в каждом методе действия, как показано в листинге 16-1.
Листинг 16-1: Явная проверка авторизации в методах действия
namespace SportsStore.WebUI.Controllers
{
public class AdminController : Controller
{
// ... instance variables and constructor public ViewResult Index()
{
if (!Request.IsAuthenticated)
{
FormsAuthentication.RedirectToLoginPage();
}
// ...rest of action method
}
public ViewResult Create()
{
if (!Request.IsAuthenticated)
{
FormsAuthentication.RedirectToLoginPage();
}
// ...rest of action method
}
public ViewResult Edit(int productId)
{
if (!Request.IsAuthenticated)
{
FormsAuthentication.RedirectToLoginPage();
}
// ...rest of action method
}
// ... other action methods
}
}
Как видите, при таком подходе много повторов, и вместо этого разумнее использовать фильтр, как показано в листинге 16-2.
387

Листинг 16-2: Применяем фильтр
namespace SportsStore.WebUI.Controllers
{
[Authorize]
public class AdminController : Controller
{
// ... instance variables and constructor public ViewResult Index()
{
// ...rest of action method
}
public ViewResult Create()
{
// ...rest of action method
}
public ViewResult Edit(int productId)
{
// ...rest of action method
}
// ... other action methods
}
}
Фильтры – это атрибуты .NET, которые добавляют дополнительные этапы в конвейер обработки запроса. В листинге 16-2 мы использовали фильтр Authorize, который имеет тот же эффект, как и все дублирующиеся проверки в листинге 16-1.
Памятка по атрибутам .NET
Атрибуты - это специальные классы .NET, наследующие от System.Attribute. Вы можете применить их к другим элементам кода, в том числе классам, методам, свойствам и полям. Их цель состоит в том, чтобы внедрить дополнительную информацию в скомпилированный код, которую можно позже прочитать в среде выполнения.
В C# для применения атрибутов используются квадратные скобки, а заполнить их общедоступные свойства можно путем присвоения значений параметрам (например, [MyAttribute(SomeProperty=value)]). Согласно соглашению по именам компилятора C#, если имя класса атрибута заканчивается на Attribute, эту часть можно опустить (например, применить фильтр AuthorizeAttribute можно, записав только [Authorize]).
Четыре основных типа фильтров
MVC Framework поддерживает четыре типа фильтров, которые позволяют внедрять логику в разное время процесса обработки запроса. Они описаны в таблице 16-1.
Таблица 16-1: Типы фильтров MVC Framework
Тип фильтра
Фильтр
авторизации
Фильтр
действий
Фильтр
результатов
Интерфейс |
|
Реализация по |
Описание |
|
умолчанию |
||
|
|
|
|
|
|
|
|
|
|
|
Запускается вначале, перед любым |
IAuthorizationFilter |
|
AuthorizeAttribute |
другим фильтром или методом |
|
|
|
действия |
|
|
|
|
IActionFilter |
|
ActionFilterAttribute |
Запускается до и после метода |
|
действия |
||
|
|
|
|
IResultFilter |
|
ActionFilterAttribute |
Запускается до и после выполнения |
|
результата действия |
||
|
388 |
|

Тип фильтра |
|
Интерфейс |
|
Реализация по |
|
|
умолчанию |
||
|
|
|
|
Фильтр
IExceptionFilter HandleErrorAttribute
исключений
Описание
Запускается только в том случае, если другой фильтр, метод действия или результат действия генерирует исключение
Перед тем, как вызвать действие, MVC Framework проверяет определение метода на наличие атрибутов, реализующих перечисленные в таблице 16-1 интерфейсы. Если они есть, то в соответствующий момент обработки запроса вызывается метод, определенный этим интерфейсом. Платформа включает стандартные классы атрибутов, которые реализуют интерфейсы фильтров. Позже в данной главе мы научимся использовать эти классы.
Примечание
Класс ActionFilterAttribute реализует и интерфейс IActionFilter, и
IResultFilter. Этот класс является абстрактным, что подразумевает
необходимость его реализовать. Другие классы с полезными функциями, такие как
AuthorizeAttribute и HandleErrorAttribute, можно использовать, не создавая
производный класс.
Применяем фильтры к контроллерам и методам действий
Фильтры можно применить к отдельным методам действий или к целому контроллеру. В листинге 16-2 мы применили фильтр Authorize к классу AdminController. Это имеет тот же эффект, как и применение его к каждому методу действия в контроллере, что показано в листинге 16-3.
Листинг 16-3: Применяем фильтр к методам действий индивидуально
namespace SportsStore.WebUI.Controllers
{
public class AdminController : Controller
{
// ... instance variables and constructor [Authorize]
public ViewResult Index()
{
// ...rest of action method
}
[Authorize]
public ViewResult Create()
{
// ...rest of action method
}
// ... other action methods
}
}
Вы можете одновременно применить несколько фильтров, причем на разных уровнях, то есть и к контроллеру, и отдельному методу действия. В листинге 16-4 показано использование трех различных фильтров.
389

Листинг 16-4: Применяем несколько фильтров в классе контроллера
[Authorize(Roles = "trader")] // applies to all actions public class ExampleController : Controller
{
[ShowMessage] // applies to just this action [OutputCache(Duration = 60)] // applies to just this action public ActionResult Index()
{
// ... action method body
}
}
Некоторые фильтры из листинга 16-4 принимают параметры. Мы рассмотрим, как они работают, когда будем изучать виды фильтров по отдельности.
Примечание
Если вы определили пользовательский базовый класс для контроллеров, фильтры, примененные к базовому классу, будут использоваться и в производных классах.
Создание проекта для примера
Для этой главы мы создали новый проект MVC под названием Filters на шаблоне Empty. Мы создали контроллер Home с методами действий, которые показаны в листинге 16-5. В этой главе мы сосредоточены только на контроллерах, так что из методов действий будем возвращать строковые значения, а не объекты ActionResult. Из-за этого MVC Framework будет отправлять строковые значения непосредственно в браузер, минуя движок представлений Razor. (Мы сделали это для простоты, но предполагая, что в реальных проектах вы будете использовать представления).
Листинг 16-5: Контроллер Home в проекте Filters
using System;
using System.Collections.Generic; using System.Linq;
using System.Web; using System.Web.Mvc;
namespace Filters.Controllers
{
public class HomeController : Controller
{
public string Index()
{
return "This is the Index action on the Home controller";
}
}
}
Если вы запустите приложение, то по стандартным маршрутам, определенным Visual Studio, запрошенная нашим браузером ссылка / будет соотнесена с методом действия Index контроллера Home. Вы увидите результат, показанный на рисунке 16-1.
390