
ASP_NET_MVC_4_Framework_s_primerami_na_C_dlya_p
.pdfСистема результатов действия является примером шаблона "Команда" (Command pattern). Этот паттерн описывает сценарии, когда вы храните и передаете объекты, описывающие операции,
которые должны быть выполнены. См. http://en.wikipedia.org/wiki/Command_pattern для более подробной информации.
Когда MVC получает от метода действия объект ActionResult, он вызывает метод ExecuteResult, определяемый этим объектом. Реализация результата действия затем работает для вас с объектом Response, создавая выходные данные, которые соответствуют вашим намерениям. Простым примером является класс CustomRedirectResult, показанный в листинге 15-9, который мы определили в новой папке Infrastructure нашего проекта.
Листинг 15-9: Класс CustomRedirectResult
using System;
using System.Collections.Generic; using System.Linq;
using System.Web; using System.Web.Mvc;
namespace ControllersAndActions.Infrastructure
{
public class CustomRedirectResult : ActionResult
{
public string Url { get; set; }
public override void ExecuteResult(ControllerContext context)
{
string fullUrl = UrlHelper.GenerateContentUrl(Url, context.HttpContext); context.HttpContext.Response.Redirect(fullUrl);
}
}
}
Мы взяли за основу работы этого класса способ, которым работает класс System.Web.Mvc.RedirectResult: одно из преимуществ того, что MVC является платформой с открытым исходным кодом, заключается в том, что вы можете сами посмотреть, как все это работает. Наш класс CustomRedirectResult намного проще, чем его MVC эквивалент, но этого достаточно для наших целей на данный момент.
Когда мы создаем экземпляр класса RedirectResult, мы передаем ему URL, на который мы хотим перенаправить пользователя. Метод ExecuteResult, который будет выполняться MVC фреймворком, когда наш метод действия закончит работу, получает объект Response для запроса через объект ControllerContext, который предоставляет фреймворк, и вызовет либо метод RedirectPermanent, либо Redirect, а это именно то, что мы делали вручную в листинге 15-8.
Вы можете увидеть, как мы использовали класс CustomRedirectResult, в листинге 15-10, который показывает, как мы изменили контроллер Derived.
Листинг 15-10: Использование класса CustomRedirectResult в контроллере Derived
using ControllersAndActions.Infrastructure; using System.Web.Mvc;
namespace ControllersAndActions.Controllers
{
public class DerivedController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Hello from the DerivedController Index method"; return View("MyView");
}
371
public ActionResult ProduceOutput()
{
if (Server.MachineName == "TINY")
{
return new CustomRedirectResult { Url = "/Basic/Index" };
}
else
{
Response.Write("Controller: Derived, Action: ProduceOutput"); return null;
}
}
}
}
Заметьте, что нам нужно было изменить результат действия метода, чтобы вернуть ActionResult. Мы возвращаем null, если мы не хотим, чтобы MVC что-то делал, когда наш метод действия будет выполнен. Это то же, что мы и делаем, когда не возвращаем экземпляр CustomRedirectResult.
Юнит тестирование контроллеров и действий
Многие части MVC фреймворка призваны облегчить модульное тестирование, и это особенно верно для действий и контроллеров. Есть несколько причин для этой поддержки:
Вы можете проверить действия и контроллеры вне веб-сервера. Контекстные объекты доступны через свои базовые классы (например, HttpRequestBase), для которых легко применима mock-технология.
Вам не нужно разбирать какой-либо HTML для проверки результата метода действия. Вы можете проверить возвращаемый объект ActionResult, чтобы убедиться, что вы получили ожидаемый результат.
Вам не нужно имитировать запросы клиентов. Система связывания данных модели MVC фреймворка позволяет вам писать методы действий, которые получают входные данные в качестве параметров метода. Для проверки метода действия вы просто вызываете метод действия и передаете значения параметров, которые вас интересуют.
Мы покажем вам, как создавать юнит тесты для различных видов результатов по мере продвижения по данной главе.
Не забывайте, что модульное тестирование – это не полная история. Сложные виды поведения в приложении возникают тогда, когда методы действий вызываются последовательно. Модульное тестирование лучше всего сочетается с другими видами тестирования.
Теперь, когда вы увидели, как работает наш пользовательский результат действия, мы можем переключиться на один из предоставляемых MVC, который обладает расширенным набором функций и был тщательно протестирован Microsoft. Листинг 15-11 показывает изменения, которые мы внесли.
Листинг 15-11: Использование встроенного объекта RedirectResult
...
public ActionResult ProduceOutput() {
return new RedirectResult("/Basic/Index");
}
...
372
Мы удалили условный оператор из метода действия, а это значит, что если вы запустите приложение и перейдите к методу /Derived/ProduceOutput, ваш браузер будет перенаправлен на URL
/Basic/Index.
Чтобы упростить код метода действия, класс Controller включает в себя методы для создания различных видов объектов ActionResult. Так, в качестве примера, мы можем добиться результата из листинга 15-11, вернув результат метода Redirect, как показано в листинге 15-12.
Листинг 15-12: Использование метода контроллера для создания результата действия
...
public ActionResult ProduceOutput() {
return Redirect("/Basic/Index");
}
...
Нет ничего особенно сложного в системе результатов действий, но это поможет вам в конечном итоге получить более простой, более чистый и более последовательный код, который легче читать и который проще для модульного тестирования.
В случае переадресации, например, вы можете просто проверить, что метод действия возвращает экземпляр RedirectResult и что свойство Url содержит ожидаемую цель.
MVC фреймворк содержит ряд встроенных типов результатов действий, которые показаны в таблице 15-2. Все эти типы являются производными от ActionResult, и многие из них имеют удобные вспомогательные методы в классе Controller. В следующих разделах мы покажем вам, как использовать эти типы результатов.
Таблица 15-2: Встроенные типы ActionResult
Тип |
Описание |
Отображает указанный шаблон ViewResult представления или шаблон по
умолчанию
Отображает указанный шаблон PartialViewResult частичного представления или шаблон
по умолчанию
Работает с HTTP перенаправлением
RedirectToRouteResult 301 или 302 на метод действия или конкртеный роут, генерируя URL в
соответствии с вашей конфигурацией
Работает с HTTP перенаправлением
RedirectResult
301 или 302 на конкретный URL
Устанавливает ответный код HTTP статуса на 401 (что означает "не авторизирован"), который вызывает
HttpUnauthorizedResult активный механизм аутентификации (form-аутентификацию или Windowsаутентификацию), чтобы попросить посетителя войти в систему
Вспомогательные методы
View
PartialView
RedirectToAction,
RedirectToActionPermanent,
RedirectToRoute,
RedirectToRoutePermanent
Redirect, RedirectPermanent
Нет
HttpNotFoundResult |
Возвращает HTTP ошибку 404—Not |
HttpNotFound |
|
found |
|||
|
|
||
HttpStatusCodeResult Возвращает указанный HTTP код |
Нет |
373
Тип |
Описание |
Вспомогательные методы |
EmptyResult |
Ничего не делает |
Нет |
Возвращение HTML при отображении представления
Наиболее распространенный вид ответа от метода действия заключается в создании HTML и отправке его браузеру. При использовании системы результатов действий вы делаете это путем создания экземпляра класса ViewResult, который определяет желаемое представление для генерирования HTML, как показано в листинге 15-13.
Листинг 15-13: Указание отображаемого представления при помощи ViewResult
using System.Web.Mvc;
namespace ControllersAndActions.Controllers
{
public class ExampleController : Controller
{
public ViewResult Index()
{
return View("Homepage");
}
}
}
В этом листинге мы используем вспомогательный метод View для создания экземпляра класса ViewResult, который затем возвращается как результат действия метода.
Примечание
Обратите внимание, что возвращаемым типом метода действия в листинге является ViewResult. Метод компилировался бы и работал так же хорошо, если бы мы указали более общий тип ActionResult. На самом деле некоторые MVC программисты определяют результат каждого метода действия как ActionResult, даже если они знают, что всегда будет возвращаться более конкретный тип. В следующих примерах мы все же будем более усердны, чтобы показать вам, как можно использовать каждый тип результата, но мы, как правило, более спокойно относимся к этому в реальных проектах.
Вы указываете представление, которое нужно отобразить, с помощью параметра метода View. В этом примере мы указали представление Homepage.
Примечание
Мы могли бы создать объект ViewResult явно, при помощи return new ViewResult { ViewName = "Homepage" };. Это вполне приемлемый подход, но мы предпочитаем использовать вспомогательные методы, определенные в классе Controller.
Когда MVC вызывает метод ExecuteResult объекта ViewResult, начинается поиск представления, которое вы указали. Если вы используете в вашем проекте области, то фреймворк будет искать в следующих местах:
/Areas/<AreaName>/Views/<ControllerName>/<ViewName>.aspx
/Areas/<AreaName>/Views/<ControllerName>/<ViewName>.ascx
/Areas/<AreaName>/Views/Shared/<ViewName>.aspx
/Areas/<AreaName>/Views/Shared/<ViewName>.ascx
/Areas/<AreaName>/Views/<ControllerName>/<ViewName>.cshtml
374
/Areas/<AreaName>/Views/<ControllerName>/<ViewName>.vbhtml
/Areas/<AreaName>/Views/Shared/<ViewName>.cshtml
/Areas/<AreaName>/Views/Shared/<ViewName>.vbhtml
Вы можете видеть из списка, что фреймворк ищет представления, которые были созданы для движка представления ASPX (расширения файлов .aspx и .ascx), даже если был указан Razor, когда создавался проект. Также фреймворк ищет C# и Visual Basic .NET шаблоны Razor (.cshtml файлы – это C#, а .vbhtml – Visual Basic; синтаксис Razor такой же в этих файлах, но фрагменты кода, как можно понять из названий, написаны на разных языках). MVC проверяет по очереди, существует ли каждый из этих файлов. Как только он находит соответствие, он использует представление, чтобы отобразить результат метода действия.
Если вы не используете области или вы используете области, но ни один из файлов в предыдущем списке не был найден, тогда фреймворк продолжит свой поиск в следующих местах:
/Views/<ControllerName>/<ViewName>.aspx
/Views/<ControllerName>/<ViewName>.ascx
/Views/Shared/<ViewName>.aspx
/Views/Shared/<ViewName>.ascx
/Views/<ControllerName>/<ViewName>.cshtml
/Views/<ControllerName>/<ViewName>.vbhtml
/Views/Shared/<ViewName>.cshtml
/Views/Shared/<ViewName>.vbhtml
Еще раз, как только MVC фреймворк проверит расположение файлов и найдет нужный файл, то поиск прекратится, и найденное представление будет использоваться для отображения ответа клиенту.
Мы не используем в нашем примере приложения области, поэтому фреймворк будет сразу искать в /Views/Example/Index.aspx. Обратите внимание, что опущена часть Controller имени класса, поэтому создание ViewResult в ExampleController приводит к поиску каталога с именем Example.
Юнит тест: отображение представления
Для проверки представления, которое отображает метод действия, вы можете проверить объект ViewResult, который он возвращает. Возможно, это не совсем тот тест, который вы ожидали увидеть, ведь вы не проходите по всему процессу до проверки сгенерированного HTML, но он достаточно хорош, пока у вас есть уверенность в том, что MVC фреймворк работает должным образом. Мы добавили новый проект Unit Test в Visual Studio и добавили файл ActionTests.cs, чтобы в нем содержались наши методы тестирования.
Первая ситуация для проверки появляется тогда, когда метод действия выбирает конкретное представление:
public ViewResult Index() { return View("Homepage");
}
Вы можете определить, какое представление было выбрано, прочитав свойство ViewName объекта ViewResult, как показано в данном тестовом методе.
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting; using ControllersAndActions.Controllers;
using System.Web.Mvc;
375
namespace ControllersAndActions.Tests
{
[TestClass]
public class ActionTests
{
[TestMethod]
public void ViewSelectionTest()
{
//Arrange - создание контроллера
ExampleController target = new ExampleController();
//Act - вызов метода действия
ViewResult result = target.Index(); // Assert - проверка результата
Assert.AreEqual("Homepage", result.ViewName);
}
}
}
Небольшое различие возникает тогда, когда вы тестируете метод действия, который выбирает представление по умолчанию:
public ViewResult Index() { return View();
}
В такой ситуации вы должны принять пустую строку ("") для названия представления:
Assert.AreEqual("", result.ViewName);
Последовательность директорий, в которой MVC ищет представление, является еще одним примером соглашения о конфигурации. Вам не нужно регистрировать файлы представлений при помощи фреймворка. Вы просто добавляете их в одно из множества известных мест, и фреймворк их найдет. Мы можем пройти на шаг вперед в этом соглашении, опуская имя представления, которое мы хотим отобразить, когда мы вызываем метод View, как показано в листинге 15-14.
Листинг 15-14: Создание ViewResult без указания представления
using System.Web.Mvc; using System;
namespace ControllersAndActions.Controllers
{
public class ExampleController : Controller
{
public ViewResult Index()
{
return View();
}
}
}
Когда мы делаем так, MVC предполагает, что мы хотим отобразить представление, которое имеет такое же имя, что и метод действия. Это означает, что вызов метода View в листинге 15-14 запускает поиск представления Index.
Примечание
Результат этого заключается в том, что ищется представление, которое имеет такое же имя, что и метод действия, но имя представления фактически
376

определяется из значения RouteData.Values["action"], которое мы объяснили как
часть системы маршрутизации в главах 13 и 14.
Есть несколько перегруженных версий метода View. Они соответствуют настройкам различных свойств объекта ViewResult, который создается. Например, вы можете переопределить макет, который используется представлением, явно указав альтернативу:
...
public ViewResult Index() {
return View("Index", "_AlternateLayoutPage");
}
...
Указание представления по его пути
Подход с использованием соглашения по именованиям удобен и прост, но таким образом ограничивается число представлений, которые вы можете отобразить. Если вы хотите отобразить конкретное представление, вы можете сделать это, если явно укажете путь к нему и обойдете фазу поиска. Вот пример:
using System.Web.Mvc;
namespace ControllersAndActions.Controllers
{
public class ExampleController : Controller
{
public ViewResult Index()
{
return View("~/Views/Other/Index.cshtml");
}
}
}
При указании представления наподобие этого путь должен начинаться с / или ~/ и включать в себя расширение имени файла (например, .cshtml для представлений Razor, содержащих C# код).
Если вы собираетесь использовать эту функцию, мы предлагаем вам сделать маленькую паузу и спросить себя, чего вы пытаетесь достичь. Если вы пытаетесь отобразить представление, которое принадлежит другому контроллеру, то лучше перенаправить пользователя на метод действия в этом контроллере (см. раздел "Перенаправление на метод действия" далее в этой главе). Если вы пытаетесь обойти схему именований, потому что данный способ не подходит тому, как вы организовали ваш проект, то см. главу 18, в которой объясняется, как реализовать пользовательскую последовательность поиска.
Передача данных из метода действия в представление
Нам часто нужно передать данные из метода действия в представление. MVC Framework предлагает несколько различных способов сделать это, о чем мы и поговорим в следующих разделах. В этих разделах мы коснемся темы представлений, которые мы подробно рассмотрим в главе 18. В этой главе мы затронем только тот функционал представлений, который необходим для демонстрации интересующих нас возможностей контроллеров.
Предоставление объекта модели представления
Вы можете отправить объект в представление, передав его в качестве параметра методу View, как показано в листинге 15-15.
377

Листинг 15-15: Указание объекта модели представления
...
public ViewResult Index() { DateTime date = DateTime.Now; return View(date);
}
...
Мы передали объект DateTime в качестве модели представления. Мы можем обратиться к объекту в представлении, используя ключевое слово Razor Model, как показано в листинге 15-16.
Листинг 15-16: Доступ к модели представления в Razor представлении
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
The day is: @(((DateTime)Model).DayOfWeek)
Представление, показанное в листинге 15-16, известно как нетипизированное или слабо типизированное. Представление ничего не знает об объекте модели представления и обрабатывает его как экземпляр object. Чтобы получить значение свойства DayOfWeek, нам нужно привести объект к экземпляру DateTime. Это работает, но представление получается «грязным». Мы можем изменить это, создавая строго типизированные представления, когда мы говорим представлениям, каким будет тип объекта модели представления, как показано в листинге 15-17.
Листинг 15-17: Строго типизированное представление
@model DateTime
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
The day is: @Model.DayOfWeek
Мы указываем тип модели представления с помощью ключевого слова Razor model. Обратите внимание, что мы используем строчную m, когда указываем тип модели, и заглавную M, когда мы читаем значение. Не только это помогает очистить наше представление, также Visual Studio поддерживает IntelliSense для строго типизированных представлений, как показано на рисунке 15-3.
Рисунок 15-3: Поддержка IntelliSense для строго типизированных представлений
378
Юнит тест: объекты модели представления
Вы можете получить доступ к объекту модели представления, переданного методом действия представлению, при помощи свойства ViewResult.ViewData.Model. Вот простой метод действия:
public ViewResult Index() {
return View((object)"Hello, World");
}
Этот метод действия передает string в качестве объекта модели представления. Мы привели его к object, так что компилятор не думает, что мы хотим перегруженный вариант View, который указывает имя представления. Мы можем получить доступ к объекту модели представления с помощью свойства ViewData.Model, как показано в данном тестовом методе:
...
[TestMethod]
public void ViewSelectionTest() {
//Arrange - создание контроллера
ExampleController target = new ExampleController();
//Act - вызов метода действия
ViewResult result = target.Index(); // Assert - проверка результата
Assert.AreEqual("Hello, World", result.ViewData.Model);
}
...
Передача данных при помощи ViewBag
Мы представили ViewBag в главе 2. Эта возможность позволяет определять произвольные свойства динамического объекта и дает доступ к ним в представлении. Доступ к динамическому объекту можно получить через свойство Controller.ViewBag, как показано в листинге 15-18.
Листинг 15-18: Использование ViewBag
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();
}
}
}
В листинге мы определили свойства Message и Date, просто присвоив им значения. До этого момента таких свойств не существовало, и мы не делали никаких приготовлений для их создания. Чтобы прочитать данные обратно в представлении, мы просто получаем те же свойства, которые мы установили в методе действия, как показано в листинге 15-19.
Листинг 15-19: Чтение данных из ViewBag
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
379
The day is: @ViewBag.Date.DayOfWeek
<p />
The message is: @ViewBag.Message
ViewBag имеет преимущество перед использованием объекта модели представления в том, что таким образом легко отправлять несколько объектов в представление. Если бы мы были ограничены в использовании моделей представления, то мы должны были бы создать новый тип, который имеет членов string и DateTime, чтобы получить тот же результат, что и в листингах 15-18 и 15-19.
При работе с динамическими объектами вы можете ввести в представление любую последовательность вызовов методов и свойств:
The day is: @ViewBag.Date.DayOfWeek.Blah.Blah.Blah
Visual Studio не обеспечивает поддержку IntelliSense для любых динамических объектов, в том числе ViewBag, и такие ошибки, как эта, не будут показаны, пока представление не будет отображено.
Нам нравится гибкость ViewBag, но мы склонны придерживаться строго типизированных представлений. Нет никаких ограничений, которые могли бы остановить нас от использования и моделей представления, и ViewBag в том же представлении. Обе эти возможности могут реализованы рядом друг с другом без проблем.
Юнит тест: ViewBag
Вы можете прочитать значения из ViewBag через свойство ViewResult.ViewBag. Следующий тестовый метод предназначен для метода действия из листинга 15-18:
[TestMethod]
public void ViewSelectionTest() {
//Arrange - создание контроллера
ExampleController target = new ExampleController();
//Act - вызов метода действия
ActionResult result = target.Index(); // Assert - проверка результата
Assert.AreEqual("Hello", result.ViewBag.Message);
}
...
Выполнение перенаправлений
Частый результат метода действия заключается не в том, чтобы произвести какие-либо выходные данные напрямую, а в том, чтобы перенаправить браузер пользователя на другой URL. В большинстве случаев этот URL является еще одним методом действие в приложении, который генерирует выходные данные, которые должны быть показаны пользователю.
Паттерн POST/REDIRECT/GET
Наиболее часто перенаправление используется в методах действия, которые обрабатывают запросы HTTP POST. Как мы упоминали в предыдущей главе, запросы POST используются тогда, когда вы хотите изменить состояние приложения. Если вы просто возвращаете HTML после обработки запроса, вы рискуете, что пользователь будет нажимать кнопку перезагрузки браузера и повторит отправку форму во второй раз, вызывая неожиданные и нежелательные результаты.
Чтобы избежать этой проблемы, вы можете следовать паттерну Post/Redirect/Get. В этом паттерне вы получаете POST запрос, обрабатывает его, а затем перенаправляете браузер так, чтобы GET запрос был
380