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

ASP_NET_MVC_4_Framework_s_primerami_na_C_dlya_p

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

Избегайте символов, кода и последовательности символов. Если вы хотите разделить слова, используйте дефисы (как в /my-great-article). Подчеркивания недружелюбны, а закодированные пробелы странные (/my+great+article) или отвратительные

(/my%20great%20article).

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

Будьте последовательны. Придерживайтесь одного URL формата во всем приложении.

URL должны быть краткими, легко набираемыми, редактируемыми (чтобы пользователь мог сам работать с URL) и надежными, также они должны визуализировать структуру сайта. Якоб Нильсен, гуру юзабилити, развивает эту тему на http://www.useit.com/alertbox/990321.html. Тим Бернерс-Ли, изобретатель Интернета, дает подобный совет (см. http://www.w3.org/Provider/Style/URI).

GET и POST: выберите правильный

Негласное правило гласит, что запросы GET следует использовать для получения информации «только для чтения» (read-only), в то время как POST запросы должны быть использованы для любой операции, которая изменяет состояние приложения. В терминах стандартизации, GET запросы служат для безопасного взаимодействия (только для получения информации), а POST запросы для небезопасного взаимодействия (принятие решения или изменение чего-то). Эти соглашения устанавливаются World Wide Web Consortium (W3C), на http://www.w3.org/Protocols/rfc2616/rfc2616sec9.html.

GET запросы являются адресуемыми: вся информация содержится в URL, так что можно создавать закладки и делать ссылки на эти адреса.

Не используйте GET запросы для операций, изменяющих состояние. Многие веб-разработчики узнали это на собственном горьком опыте в 2005 году, когда был публично выпущен Google Web Accelerator. Это приложение прошло по всему контенту, для которого были назначены ссылки, что является законным для HTTP GET запросов, потому что они должны быть безопасными. К сожалению, многие веб-разработчики проигнорировали HTTP соглашение и разместили простые ссылки для "удалить" или "добавить в корзину" в своих приложениях. Начался хаос.

Одна из компаний посчитала, что их система управления контентом была целю повторяющихся атак, потому что все содержимое снова и снова удалялось. Позже они выяснили, что поисковой робот натолкнулся на URL административной страницы и прошел по всем ссылкам для удаления. Аутентификация может защитить вас от этого, но она не сможет защитить вас от поисковых роботов (краулеров).

Резюме

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

Вследующей главе мы обратимся к контроллерам и действиям, которые являются фактически сердцем MVC модели. Мы объясним, как они работают, и покажем вам, как их использовать, чтобы получить лучшие результаты для приложения.

361

Контроллеры и методы действий

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

ВASP.NET MVC фреймворке контроллеры – это .NET классы, содержащие логику, необходимую для обработки запроса. В главе 3 мы объяснили, что роль контроллера заключается в инкапсуляции логики приложения. Это обозначает, что контроллеры отвечают за обработку входящих запросов, выполняя операции по доменной модели и выбирая представления для отображения пользователю.

Вэтой главе мы покажем вам, как реализованы контроллеры, и представим различные способы, которыми вы можете использовать контроллеры, чтобы получать и генерировать выходные данные. MVC не ограничивает вас созданием HTML при помощи представлений, и мы обсудим тут другие доступные варианты. Мы также покажем, как методы действий облегчают выполнение модульного тестирования, и продемонстрируем, как проверять каждый вид результата методов действия.

Знакомство с контроллером

Вы видели использование контроллеров практически всех предыдущих главах. Теперь пришло время сделать небольшой шаг назад и взглянуть за кулисы. Для начала нам нужно создать проект для наших примеров.

Подготовка нашего проекта

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

Совет

Не забудьте создать проект модульного теста, если вы хотите следовать примерам тестов, которые мы предлагаем в этой главе.

Создание контроллера при помощи IController

В MVC фреймворке классы контроллеров должны реализовывать интерфейс IController пространства имен System.Web.Mvc, как показано в листинге 15-1.

Листинг 15-1: Интерфейс System.Web.Mvc.IController

public interface IController

{

void Execute(RequestContext requestContext);

}

Это очень простой интерфейс. Единственный метод, Execute, вызывается при запросе, который ориентирован на класс контроллера. MVC фреймворк знает, на какой класс был нацелен запрос, прочитав значение свойства controller, полученное от роутовых данных.

362

Вы можете выбрать для создания классов контроллеров реализацию IController, но это довольно низкоуровневый интерфейс, и вы должны проделать много работы, чтобы получить что-нибудь полезное. В листинге 15-2 показан простой контроллер BasicController, на основе которого представлена эта возможность.

Листинг 15-2: Класс BasicController

using System.Web.Mvc; using System.Web.Routing;

namespace ControllersAndActions.Controllers

{

public class BasicController : IController

{

public void Execute(RequestContext requestContext)

{

string controller = (string)requestContext.RouteData.Values["controller"]; string action = (string)requestContext.RouteData.Values["action"]; requestContext.HttpContext.Response.Write(

string.Format("Controller: {0}, Action: {1}", controller, action));

}

}

}

Для создания этого класса мы щелкнули правой кнопкой мыши по папке Controllers проекта и выбрали Add -> New Class. Затем мы назвали наш контроллер BasicController и добавили код, показанный в листинге 15-2.

В нашем методе Execute мы читаем значения переменных controller и action из объекта RouteData, связанного с запросом, и записываем их в результат. Если вы запустите приложение и перейдите по /Basic/Index, вы увидите результат выполнения нашего контроллера, как показано на рисунке 15-1.

Рисунок 15-1: Результат, сгенерированный классом BasicController

Реализация интерфейса IController позволяет создать класс, который MVC распознает в качестве контроллера и которому отправляет запросы, но тут довольно трудно написать сложное приложение. MVC не определяет, как контроллер работает с запросами, что обозначает, что вы можете создать свой желаемый способ.

Совет

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

363

Создание контроллера путем наследования от класса Controller

MVC фреймворк бесконечно настраиваемый и расширяемый. Вы можете реализовать интерфейс IController для создания любого вида обработки запросов и генерации результата, который вам требуется. Не нравятся методы действий? Не важны отображаемые представления? Тогда вы можете просто взять дело в свои руки и написать лучший, более быстрый и более элегантный способ обработки запросов. Или же вы можете опираться на возможности, которые предоставила команда MVC, и это достигается наследованием ваших контроллеров от класса System.Web.Mvc.Controller.

System.Web.Mvc.Controller является классом, обеспечивающим поддержку обработки запросов, которая знакома большинству MVC разработчиков. Это то, что мы используем во всех наших примерах в предыдущих главах.

Класс Controller предоставляет три ключевые возможности:

Методы действия: Поведение контроллера разделено на множество методов (вместо того, чтобы иметь только один метод Execute()). Каждый метод действия срабатывает для определенного, «своего» URL и вызывается с параметрами, извлеченными из входящего запроса.

Результаты действия: Вы можете вернуть объект, описывая результат действия (например, отображение представления или перенаправление на другой URL или метод действия), который затем вы можете использовать по своему усмотрению. Разделение между указанием результатов и их выполнением упрощает модульное тестирование.

Фильтры: Вы можете инкапсулировать повторяющиеся виды поведения (например, аутентификацию, как вы видели в главе 11) в качестве фильтров, а затем добавлять каждый вид поведения в один или несколько контроллеров или методов действия, разместив [Attribute] в исходном коде.

Пока вам не нужно реализовывать очень конкретные требования, лучшим способом для создания контроллеров является наследование от класса Controller, и, как вы знаете, это то, что делает Visual Studio, когда создает новый класс в ответ на Add -> Controller. В листинге 15-3 показан простой контроллер, созданный таким образом. Мы назвали наш класс DerivedController.

Листинг 15-3: Простой контроллер, унаследованный от класса System.Web.Mvc.Controller

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");

}

}

}

Базовый класс Controller реализует метод действия Execute и отвечает за вызов метода действия, имя которого соответствует значению action в роутовых данных.

Класс Controller также является нашей связью с системой представления Razor. В листинге мы возвращаем результат метода View, передавая имя представления, которое мы хотим отобразить, клиенту в качестве параметра. Листинг 15-4 показывает это представление MyView.cshtml, которое расположено в папке Views/Derived.

364

Листинг 15-4: Файл MyView.cshtml

@{

ViewBag.Title = "MyView";

}

<h2>MyView</h2>

Message: @ViewBag.Message

Если вы запустите приложение и перейдете на /Derived/Index, будет выполнен метод действия, который мы определили, и будет отображено нужное представление, как показано на рисунке 15-2.

Рисунок 15-2: Результат, сгенерированный классом DerviedController

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

Получение входных данных

Контроллерам часто необходим доступ к входящим данным, таким как значения строки запроса, значения форм и параметры, полученные из входящего URL системой маршрутизации. Есть три основных способа доступа к этим данным:

Получить их из набора контекстных объектов

Получить данные, переданные в качестве параметров методу действия

Явно вызвать связывание данных модели

Здесь мы рассмотрим подходы получения входных данных для методов действия, уделяя особое внимание использованию контекстных объектов и параметров метода действия. В главе 22 мы подробно изучим связывание данных модели.

Получение данных из контекстных объектов

Самый прямой способ получить данные заключается в том, чтобы извлечь их самостоятельно. Когда вы создаете контроллер путем наследования от базового класса Controller, вы получаете доступ к набору полезных свойств для получения информации о запросе. Эти свойства включают Request, Response, RouteData, HttpContext и Server. Каждое из них предоставляет информацию о различных аспектах запроса. Эти свойства получают различные типы данных из экземпляра запроса ControllerContext (который может быть доступным через свойство Controller.ControllerContext). Мы описали некоторые из наиболее часто используемых контекстных объектов в таблице 15-1.

365

Таблица 15-1: Наиболее часто используемые контекстные объекты

Свойство

 

Тип

 

Описание

 

 

 

 

 

Request.QueryString

 

NameValueCollection

 

Переменные GET, отправленные с этим

 

 

запросом

 

 

 

 

 

 

 

 

 

Request.Form

 

NameValueCollection

 

Переменные POST, отправленные с этим

 

 

запросом

 

 

 

 

 

 

 

 

 

Request.Cookies

 

HttpCookieCollection

 

Куки, отправленные браузером с этим

 

 

запросом

 

 

 

 

 

 

 

 

 

Request.HttpMethod

 

string

 

HTTP метод (например, GET или POST),

 

 

используемый для этого запроса

 

 

 

 

 

 

 

 

 

Request.Headers

 

NameValueCollection

 

Полный набор HTTP заголовков,

 

 

отправленный с этим запросом

 

 

 

 

Request.Url

 

Uri

Request.UserHostAddressstring

RouteData.Route RouteBase

RouteData.Values RouteValueDictionary

Запрашиваемый URL

IP адрес пользователя, сделавшего запрос

Выбранная запись из RouteTable.Routes для этого запроса

Активные роутовые параметры (как полученные из URL, так и значения по умолчанию)

HttpContext.ApplicationHttpApplicationStateBaseСостояние приложения

HttpContext.Cache

 

Cache

 

Кэш приложения

 

 

 

 

 

HttpContext.Items

 

IDictionary

 

Состояние текущего запроса

 

 

 

 

 

HttpContext.Session

 

HttpSessionStateBase

 

Состояние сессии пользователя

 

 

 

 

 

User

 

IPrincipal

 

Информация об аутентификации

 

 

залогиненного пользователя

 

 

 

 

 

 

 

 

 

TempData

 

TempDataDictionary

 

Информация о временных данных текущего

 

 

пользователя

 

 

 

 

Метод действия может использовать любой из этих контекстных объектов, чтобы получить информацию о запросе, как показано в листинге 15-5, который демонстрирует гипотетический метод действия.

Листинг 15-5: Метод действия использует контекстные объекты, чтобы получить информацию о запросе

public ActionResult RenameProduct() {

//Доступ к различным свойствам контекстных объектов string userName = User.Identity.Name;

string serverName = Server.MachineName; string clientIP = Request.UserHostAddress; DateTime dateStamp = HttpContext.Timestamp;

AuditRequest(userName, serverName, clientIP, dateStamp, "Renaming product");

//Получение данных из Request.Form

string oldProductName = Request.Form["OldName"]; string newProductName = Request.Form["NewName"];

bool result = AttemptProductRename(oldProductName, newProductName); ViewData["RenameResult"] = result;

return View("ProductRenamed");

}

366

Вы можете просмотреть широкий спектр доступной контекстной информации о запросе при помощи IntelliSense (в методе действия наберите this. и просмотрите информацию во всплывающем окне) и Microsoft Developer Network (посмотрите System.Web.Mvc.Controller и его базовые классы или

System.Web.Mvc.ControllerContext).

Использование параметров метода действия

Как вы видели в предыдущих главах, методы действий могут принимать параметры. Это более аккуратный способ получения входящих данных, чем извлечение их вручную из контекстных объектов, и благодаря этому методы действий легче читать. Например, предположим, у нас есть метод действия, который использует контекстные объекты таким образом:

...

public ActionResult ShowWeatherForecast() {

string city = (string)RouteData.Values["city"];

DateTime forDate = DateTime.Parse(Request.Form["forDate"]);

// ... здесь идет реализация прогноза погоды ...

return View();

}

...

Мы можем переписать его, чтобы использовать параметры:

...

public ActionResult ShowWeatherForecast(string city, DateTime forDate) {

// ... здесь идет реализация прогноза погоды ...

return View();

}

Это не только легче читать, но это также помогает с модульным тестированием: мы можем проверить метод действия без необходимости использовать mock-технологию для свойств класса контроллера.

Для полноты картины стоит отметить, что методам действия не разрешается параметры out или ref. Это не имело бы никакого смысла, если бы разрешалось, и ASP.NET MVC просто выбросит исключение, если он увидит такие параметры.

MVC фреймворк предоставит значения для наших параметров, проверив контекстные объекты, в том числе Request.QueryString, Request.Form и RouteData.Values. Имена наших параметров рассматриваются как регистронезависимые, так что параметр метода действия city можно заполнить значением из Request.Form["City"].

Понимание того, как создаются экземпляры объектов параметров

Базовый класс Controller получает значения для параметров вашего метода действия с помощью

MVC компонентов, называемых провайдерами значений и механизмами связывания данных модели.

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

Request.QueryString, Request.Files и RouteData.Values. Затем значения передаются механизмам связывания данных, которые пытаются привязать их к типам, которые методы действий требуют в качестве параметров.

Дефолтовые механизмы связывания данных могут создать и заполнить объекты любого .NET типа, в том числе коллекции и специфические пользовательские типы для конкретного проекта. Вы видели

367

пример этого в главе 10, когда сообщения от администраторов были представлены нашему методу действия как один объект Product, даже при том что отдельные значения были рассеяны среди элементов HTML формы. Мы расскажем о провайдерах значений и механизмах связывания данных модели в главе 22.

Понимание факультативных и обязательных параметров

Если MVC фреймворк не может найти значение параметра ссылочного типа (например, string или object), все равно будет вызван метод действия, но с использованием значения null для этого параметра. Если значение не может быть найдено для параметра простого типа (например, int или double), то будет сгенерировано исключение, и метод действия не будет вызван. Вот почему это так:

Параметры простого типа являются обязательными. Чтобы сделать их необязательными, либо укажите значение по умолчанию (см. следующий раздел), либо замените тип параметра на nullable (например, int? или DateTime?). Таким образом, MVC сможет передать null, если значение будет не доступно.

Параметры ссылочного типа не являются обязательными. Чтобы сделать их обязательными (чтобы убедиться, что передается значение не-null), добавьте код в начало метода действия, который не принимает значения null. Например, если значение равно null, выбрасывается исключение ArgumentNullException.

Указание значений параметров по умолчанию

Если вы хотите обрабатывать запросы, которые не содержат значений для параметров метода действия, но вы не хотите проверять в коде наличие значений null или выбрасывать исключения, вы можете использовать дополнительные параметры C#. В листинге 15-6 показан пример.

Листинг 15-6: Использование дополнительных параметров C# для метода действия

...

public ActionResult Search(string query= "all", int page = 1) {

// ...обработка запроса...

return View();

}

...

Мы обозначаем параметры как дополнительные путем присвоения значений, когда мы их определяем. В листинге мы предоставили значения по умолчанию для параметров query и page. MVC Framework попытается получить значения из запроса для этих параметров, но если доступных значений не будет, будут использоваться значения по умолчанию, которые мы указали.

Для параметра string query, это означает, что нам не нужно проверять наличие значений null. Если в запросе, который мы обрабатываем, не задана строка запроса, тогда наш метод действия будет вызван со строкой all. Для параметра int нам не нужно беспокоиться о том, что запросы завершатся ошибками, если нет значения page. Наш метод действия будет вызываться со значением по умолчанию равным 1.

Необязательные параметры могут быть использованы для литеральных типов: это типы, которые можно определить без использования ключевого слова new, включая string, int и double.

Внимание

Если запрос содержит значение для параметра, но оно не может быть преобразовано в правильный тип (например, если пользователь дает нечисловую

368

строку для параметра int), то фреймворк передаст значение по умолчанию для этого типа параметра (например, 0 для параметра int) и зарегистрирует

предоставленное значение как ошибку валидации в специальном контекстном объекте ModelState. Если вы не проверите ошибки валидации в ModelState, вы

можете попасть в неприятную ситуацию, когда пользователь ввел неправильные данные в форму, но запрос обрабатывается, как если бы пользователь не ввел никаких данных вообще или ввел значение по умолчанию. См. главу 23 для информации о валидации и ModelState, который может быть использован, чтобы

избежать таких проблем.

Создание выходных данных

После того как контроллер завершит обработку запроса, он обычно должен сгенерировать ответ. Когда мы создавали наш пустой контроллер напрямую путем реализации интерфейса IController, мы должны были взять на себя ответственность за все аспекты обработки запроса, в том числе за создание ответа клиенту. Если мы хотим, например, отправить HTML ответ, то мы должны создать и скомпоновать HTML данные и отправить их клиенту, используя метод Response.Write. Аналогичным образом, если мы хотим перенаправить браузер пользователя на другой URL, мы должны вызвать метод Response.Redirect и передать интересующий нас URL напрямую. Оба этих подхода показаны в листинге 15-7, который демонстрирует улучшения в классе BasicController.

Листинг 15-7: Генерирование результатов в реализации IController

using System.Web.Mvc; using System.Web.Routing;

namespace ControllersAndActions.Controllers

{

public class BasicController : IController

{

public void Execute(RequestContext requestContext)

{

string controller = (string)requestContext.RouteData.Values["controller"]; string action = (string)requestContext.RouteData.Values["action"];

if (action.ToLower() == "redirect")

{

requestContext.HttpContext.Response.Redirect("/Derived/Index");

}

else

{

requestContext.HttpContext.Response.Write( string.Format("Controller: {0}, Action: {1}",

controller, action));

}

}

}

}

Вы можете использовать тот же подход, если вы унаследовали ваш контроллер от класса Controller. Класс HttpResponseBase, который возвращается, когда вы читаете свойство requestContext.HttpContext.Response в методе Execute, доступен через свойство

Controller.Response, как показано в листинге 15-8, который демонстрирует улучшения в классе

DerivedController.

Листинг 15-8: Генерирование выходных данных

using System.Web.Mvc;

369

namespace ControllersAndActions.Controllers

{

public class DerivedController : Controller

{

public ActionResult Index()

{

ViewBag.Message = "Hello from the DerivedController Index method"; return View("MyView");

}

public void ProduceOutput()

{

if (Server.MachineName == "TINY")

{

Response.Redirect("/Basic/Index");

}

else

{

Response.Write("Controller: Derived, Action: ProduceOutput");

}

}

}

}

Метод ProduceOutput использует значение свойства Server.MachineName для определения того, какой ответ отправить клиенту. (TINY – это имя одного из наших компьютеров).

Такой подход работает, но тут возникает несколько проблем:

Классы контроллеров должны содержать информацию об HTML или URL структуре, и поэтому эти классы труднее читать и поддерживать.

Трудно провести модульное тестирование контроллера, который генерирует свой ответ непосредственно в выходные данные. Вам нужно создать mock-реализацию объекта Response, а затем быть в состоянии обработать выходные данные, полученные от контроллера, для того, чтобы определить, что представляет собой результат. Это может означать, например, разбор HTML по ключевым словам, что является длительным и болезненным процессом.

Работа с мелкими деталями каждого ответа подобным образом является утомительной и может привести ко многим ошибкам. Некоторым программистам нравится абсолютный контроль, который дает создание «сырых» контроллеров, но нормальные люди довольно быстро разочаровываются в этом.

Ксчастью, MVC фреймворк предоставляет хорошую функциональную возможность, которая способствует решению всех этих вопросов, и она называется результаты действий. В следующих разделах введено понятие результата действия и показаны различные способы, которыми может быть использован результат действия для получения ответов от контроллеров.

Что такое результаты действия

MVC фреймворк использует результаты действия, чтобы разделить заявление о намерениях от выполнения этих намерений. Она работает очень просто.

Вместо того чтобы работать непосредственно с объектом Response, мы возвращаем объект, наследованный от класса ActionResult, который описывает то, какой ответ от контроллера мы хотели бы увидеть, например, отображение представления или перенаправление на другой URL или метод действия.

Примечание

370

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