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

ASP_NET_MVC_4_Framework_s_primerami_na_C_dlya_p

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

Устранение неоднозначности методов действий

Теперь, когда вы видели изнанку базового класса селектора, вам будет легче понять, как механизм вызова действий выбирает метод действия. Механизм вызова сначала просматривает список методов контроллера, которые отвечают критериям и являются методами действий. Затем он проходит через следующий процесс:

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

Механизм вызова отбрасывает методы с атрибутом селектора, который возвращает false для текущего запроса.

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

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

Обработка неизвестных действий

Если механизм вызова действий не может найти подходящий метод действия, то его метод InvokeAction возвращает false. Когда это происходит, класс Controller вызывает метод HandleUnknownAction. По умолчанию этот метод возвращает ответ 404 — Not Found. Такой ответ используется в большинстве приложений, но если вы хотите отображать что-то другое, то можете переопределить этот метод в каком-либо контроллере. В листинге 17-22 показано переопределение метода HandleUnknownAction в контроллере Home.

Листинг 17-22: Переопределяем метод HandleUnknownAction

using System.Web.Mvc;

using ControllerExtensibility.Infrastructure; using ControllerExtensibility.Models; namespace ControllerExtensibility.Controllers

{

public class HomeController : Controller

{

// ...other action methods omitted for brevity...

protected override void HandleUnknownAction(string actionName)

{

Response.Write(string.Format("You requested the {0} action", actionName));

}

}

}

Если вы запустите приложение и перейдите по ссылке, ведущей к несуществующему методу действия, то увидите ответ, показанный на рисунке 17-11.

Рисунок 17-11: Ответ для запросов к несуществующим методам действий

441

Улучшение работы приложения при помощи специальных контроллеров

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

Используем контроллеры без поддержки состояния сессии (sessionless controllers)

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

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

Не всем контроллерам нужны данные состояния сессии. Для многих контроллеров можно убрать поддержку состояния сессии и улучшить производительность приложения. Для этого создаются контроллеры без поддержки состояния сессии. У них есть два отличия от обычных контроллеров: когда они обрабатывают запрос, MVC Framework не загружает и не сохраняет состояние сессии, а перекрывающие друг друга запросы обрабатываются одновременно.

Управляем состоянием сессии в пользовательском IControllerFactory

Как уже упоминалось в начале этой главы, интерфейс IControllerFactory содержит метод GetControllerSessionBehaviour, который возвращает значение из перечисления SessionStateBehaviour. Это перечисление содержит четыре значения, которые определяют конфигурацию состояния сессии контроллера. Они описаны в таблице 17-3.

Таблица 17-3: Значения перечисления SessionStateBehavior

ЗначениеОписание

Использует стандартное поведение ASP.NET, то есть определяет конфигурацию состояния

Default сессии из HttpContext.

RequiredРазрешает чтение и запись состояния сессии.

ReadOnlyРазрешает только чтение состояния сессии.

DisabledСостояние сессии отключено.

Фабрика контроллеров, которая реализует интерфейс IControllerFactory, напрямую устанавливает поведение состояния сессии для контроллеров, возвращая значения SessionStateBehavior из метода GetControllerSessionBehavior. Параметрами этого метода являются объект RequestContext и string, содержащий имя контроллера. Вы можете вернуть любое из четырех

442

значений, приведенных в таблице, причем для разных контроллеров можно возвращать разные значения. Мы изменили реализацию метода GetControllerSessionBehavior в классе CustomControllerFactory, который создали ранее в данной главе, как показано в листинге 17-23.

Листинг 17-23: Определяем поведение состояния сессии для контроллера

public SessionStateBehavior GetControllerSessionBehavior( RequestContext requestContext,

string controllerName)

{

switch (controllerName)

{

case "Home":

return SessionStateBehavior.ReadOnly; case "Product":

return SessionStateBehavior.Required; default:

return SessionStateBehavior.Default;

}

}

Управляем состоянием сессии с помощью DefaultControllerFactory

Используя встроенную фабрику контроллеров, вы можете управлять состоянием сеанса, применяя атрибут SessionState к отдельным классам контроллеров, как показано в листинге 17-24 на примере нового контроллера FastController.

Листинг 17-24: Используем атрибут SessionState

using System.Web.Mvc;

using System.Web.SessionState;

using ControllerExtensibility.Models; namespace ControllerExtensibility.Controllers

{

[SessionState(SessionStateBehavior.Disabled)] public class FastController : Controller

{

public ActionResult Index()

{

return View("Result", new Result

{

ControllerName = "Fast ", ActionName = "Index"

});

}

}

}

Атрибут SessionState применяется к классу контроллера и влияет на все его действия. Единственный параметр атрибута – это значение из перечисления SessionStateBehavior (см. таблицу 17-3). В этом примере мы отключили состояние сессии, что означает, что если мы попытаемся установить в контроллере значение сессии:

Session["Message"] = "Hello";

или прочитать информацию из состояния сессии в представлении:

Message: @Session["Message"]

MVC Framework выбросит исключение при вызове действия или визуализации представления.

443

Подсказка

Когда состояние сеанса отключено, свойство HttpContext.Session возвращает значение null.

Если вы указали поведение ReadOnly, то сможете прочитать значения, установленные другими контроллерами, но все равно получите исключение среды выполнения, если попытаетесь их установить или изменить. Подробную информацию о сессии можно получить через объект HttpContext.Session, но попытка изменить значения приведет к ошибке.

Подсказка

Если вы просто хотите передать данные из контроллера в представление, рекомендуется использовать объект ViewBag, на который атрибут SessionState

не влияет.

Используем асинхронные контроллеры

Базовая платформа ASP.NET поддерживает пул потоков .NET, которые используются для обработки клиентских запросов. Этот пул называется пулом рабочих потоков (worker thread pool), а потоки, соответственно, рабочими (worker threads). При получении запроса рабочий поток вызывается из пула и обрабатывает запрос.

После обработки запроса рабочий поток возвращается в пул и может обрабатывать новые запросы по мере их поступления. Пулы потоков имеют два ключевых преимущества для приложений ASP.NET:

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

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

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

Примечание

В этом разделе мы предполагаем, что вы знакомы с Task Parallel Library (TPL). Если вы хотите узнать больше о TPL, прочтите книгу Адама на эту тему, Pro .NET Parallel Programming in C# издательства Apress.

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

444

Внимание!

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

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

Примечание

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

Создаем пример

Чтобы начать изучение асинхронных контроллеров, мы продемонстрируем вам проблему, которую они могут решить. В листинге 17-25 показан обычный синхронный контроллер под названием RemoteData, который мы добавили в проект.

Листинг 17-25: Проблемный синхронный контроллер

using System.Web.Mvc;

using ControllerExtensibility.Models; namespace ControllerExtensibility.Controllers

{

public class RemoteDataController : Controller

{

public ActionResult Data()

{

RemoteService service = new RemoteService(); string data = service.GetRemoteData(); return View((object)data);

}

}

}

Этот контроллер содержит метод действия, Data, который создает экземпляр класса модели RemoteService и вызывает в нем метод GetRemoteData. Этот метод отнимает много времени и снижает активность процессора. Класс RemoteService показан в листинге 17-26.

Листинг 17-26: Сущность модели с помощью трудоемким методом

using System.Threading;

namespace ControllerExtensibility.Models

445

{

public class RemoteService

{

public string GetRemoteData()

{

Thread.Sleep(2000);

return "Hello from the other side of the world";

}

}

}

Okay, здесь мы просто подделали метод GetRemoteData. В реальном мире этот метод мог бы извлекать сложные данные через медленное сетевое подключение, но для простоты мы использовали метод Thread.Sleep для моделирования двух-секундной задержки. Мы также создали простое представление под названием Data.cshtml, которое показано в листинге 17-27.

Листинг 17-27: Представление Data

@model string @{

Layout = null;

}

<!DOCTYPE html> <html>

<head>

<meta name="viewport" content="width=device-width" /> <title>Data</title>

</head>

<body>

<div>

Data: @Model </div>

</body>

</html>

Если вы запустите приложение и перейдите по ссылке RemoteData/Data, будет вызван метод действия, создан объект RemoteService и отправлен вызов к методу GetRemoteData. Через две секунды (которые имитируют выполнение реальной операции) метод GetRemoteData возвращает данные, они передаются представлению и визуализируются, как показано на рисунке 17-12.

Рисунок 17-12: Переход по ссылке /RemoteData/Data

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

Создаем асинхронный контроллер

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

446

System.Web.Mvc.Async.IAsyncController, который является асинхронным эквивалентом IController. Мы не собираемся разбирать данный подход, потому что для этого потребуется объяснять много функций параллельного программирования .NET.

Подсказка

Не все действия в асинхронных контроллерах должны быть асинхронными. Вы можете включать в них и синхронные методы, и они будут работать нормально.

Мы хотим, чтобы в центре нашего внимания оставался MVC Framework, и поэтому продемонстрируем второй подход: наследовать класс контроллера от

System.Web.Mvc.AsyncController, который реализует IAsyncController. В предыдущей версии

.NET Framework создание асинхронных контроллеров было сложным процессом и требовало разделения действия на два метода. Новые ключевые слова await и async, о которых мы рассказывали в главе 4, намного упростили этот процесс: вы создаете новый объект Task и ждете его ответа, как показано в листинге 17-28.

Подсказка

Старая техника создания асинхронных методов действий все еще поддерживается, хотя подход, который мы описываем здесь, гораздо проще и надежнее. Единственным напоминанием о старом подходе является то, что вы не можете использовать имена методов действий, которые заканчиваются на Async (например IndexAsync) или

Completed (например IndexCompleted).

Листинг 17-28: Создаем асинхронный контроллер без изменения вызываемых им методов

using System.Web.Mvc;

using ControllerExtensibility.Models; using System.Threading.Tasks;

namespace ControllerExtensibility.Controllers

{

public class RemoteDataController : AsyncController

{

public async Task<ActionResult> Data()

{

string data = await Task<string>.Factory.StartNew(() =>

{

return new RemoteService().GetRemoteData();

});

return View((object)data);

}

}

}

Мы выделили изменения, которые делают контроллер RemoteData асинхронным. Кроме изменения базового класса AsyncController, мы провели рефакторинг метода действия так, чтобы он возвращал Task<ActionResult>, применили ключевые слова async и await и создали Task<string>, который будет вызывать метод GetRemoteData.

447

Используем асинхронные методы в контроллере

С помощью асинхронных контроллеров можно использовать асинхронные методы где угодно в приложении. Чтобы это продемонстрировать, мы добавили асинхронный метод в класс RemoteService, как показано в листинге 17-29.

Листинг 17-29: Добавляем асинхронный метод в класс RemoteService

using System.Threading; using System.Threading.Tasks;

namespace ControllerExtensibility.Models

{

public class RemoteService

{

public string GetRemoteData()

{

Thread.Sleep(2000);

return "Hello from the other side of the world";

}

public async Task<string> GetRemoteDataAsync()

{

return await Task<string>.Factory.StartNew(() =>

{

Thread.Sleep(2000);

return "Hello from the other side of the world"; });

}

}

}

Результатом метода GetRemoteDataAsync является Task<string>, который производит такое же сообщение, как и синхронный метод по завершении. В листинге 17-30 вы можете увидеть, как мы использовали этот асинхронный метод действия в новом методе, который добавили в контроллер

RemoteData.

Листинг 17-30: Используем асинхронный метод в контроллере RemoteData

using System.Web.Mvc;

using ControllerExtensibility.Models; using System.Threading.Tasks;

namespace ControllerExtensibility.Controllers

{

public class RemoteDataController : AsyncController

{

public async Task<ActionResult> Data()

{

string data = await Task<string>.Factory.StartNew(() =>

{

return new RemoteService().GetRemoteData(); });

return View((object)data);

}

public async Task<ActionResult> ConsumeAsyncMethod()

{

string data = await new RemoteService().GetRemoteDataAsync(); return View("Data", (object)data);

}

}

}

448

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

Резюме

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

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

449

Представления

Как вы узнали из главы 15, методы действий могут возвращать объекты результатов действий ActionResult; наиболее часто используемым результатом действия является ViewResult, который запускает визуализацию представления и отправляет его клиенту.

Вы уже примерно знаете, для чего нужны представления, ведь мы часто использовали их в примерах приложений. В данной главе мы углубим и уточним эти знания. Для начала мы рассмотрим, как MVC Framework обрабатывает объекты ViewResult с помощью движков представлений (view engine), а также научимся создавать пользовательские движки представлений. Далее будут описаны эффективные способы работы со встроенным движком Razor View Engine. В продолжение мы продемонстрируем, как создавать и использовать частичные представления, дочерние действия и секции Razor. Все вышеперечисленные темы очень важны для эффективной работы с MVC.

Создание пользовательского движка представления

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

Движок Razor, который мы уже использовали в этой книге, появился в третьей версии MVC. У него простой и удобный синтаксис, который мы рассмотрели в главе 5.

Движок ASPX, также известный как движок представлений Web Forms, использует синтаксис тегов Web Forms <% ...%>. Этот движок используется для поддержки совместимости в старых приложениях MVC.

Весь смысл создания пользовательского движка представлений состоит в том, чтобы с его помощью продемонстрировать работу конвейера обработки запросов и дополнить ваши знания об устройстве MVC Framework. Вы сможете оценить, какой гибкостью обладает процесс преобразования объектов ViewResult в ответ клиенту. Движки представлений реализуют интерфейс IViewEngine, который показан в листинге 18-1.

Листинг 18-1: Интерфейс IViewEngine

namespace System.Web.Mvc

{

public interface IViewEngine

{

ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName,

bool useCache);

ViewEngineResult FindView(ControllerContext controllerContext, string viewName,

string masterName, bool useCache);

void ReleaseView(ControllerContext controllerContext, IView view);

}

}

Задача движка представлений заключается в преобразовании запросов к представлениям в объекты

ViewEngineResult. Первые два метода в интерфейсе, FindView и FindPartialView, принимают

450

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