Нашли ошибку или опечатку? Выделите текст и нажмите

Поменять цветовую

гамму сайта?

Поменять
Обновления сайта
и новые разделы

Рекомендовать в Google +1

Генерация ответа из контроллеров

122
1

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

Например, для отправки HTML-ответа потребуется создать и скомпоновать HTML-данные, после чего отправить их клиенту с использованием метода Response.Write(). Аналогично, чтобы переадресовать браузер пользователя на другой URL, понадобится вызвать метод Response.Redirect() и передать ему необходимый URL. Оба подхода демонстрируются в коде, приведенном в примере ниже, в котором показаны расширения класса BasicController, который мы создали в одной из предыдущих статей с помощью реализации интерфейса 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("Контроллер: {0}, Метод действия: {1}",
                    controller, action));
            }
        }
    }
}

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

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

namespace ControllersAndActions.Controllers
{
    public class DerivedController : Controller
    {
        public ActionResult Index()
        {
            // ...
        }

        public void ProduceOutput()
        {
            if (Server.MachineName == "ProfessorWeb")
                Response.Redirect("/Basic/Index");
            else
                Response.Write("Контроллер: Derived, Метод действия: ProduceOutput");
        } 
    }
}

Метод ProduceOutput() использует значение свойства Server.MachineName для принятия решения о том, какой ответ отправлять клиенту. ("ProfessorWeb" - это имя моей машины разработки.)

Хотя такой подход генерации ответа пользователю работает, с ним связано несколько проблем:

  • Классы контроллеров должны содержать сведения о структуре HTML или URL, что усложняет чтение и сопровождение классов.

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

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

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

Результаты действий

Результаты действий в MVC Framework используются для отделения заявлений о намерениях от выполнения намерений (извиняюсь за тавтологию). Концепция покажется простой после того, как вы освоитесь с ней, но она требует определенного времени на понимание из-за некоторой доли косвенности.

Вместо того чтобы иметь дело напрямую с объектом Response, методы действий возвращают объект класса, производного от ActionResult, который описывает, каким должен быть ответ из контроллера - например, визуализация представления либо перенаправление на другой URL или метод действия. Однако (это и есть та самая косвенность) ответ напрямую не генерируется. Взамен создается объект ActionResult, который MVC Framework обрабатывает для получения результата после того, как был вызван метод действия.

Система результатов действий является примером шаблона проектирования Command (Команда). Этот шаблон представляет сценарии, в рамках которых вы сохраняете и передаете объекты, описывающие выполняемые операции.

Когда инфраструктура MVC Framework получает объект ActionResult от метода действия, она вызывает метод ExecuteResult(), определенный в классе этого объекта. Реализация результатов действий затем работает с объектом Response, генерируя вывод, который соответствует вашему намерению. Чтобы продемонстрировать это в работе, создадим папку Infrastructure и добавим в нее новый файл класса по имени CustomRedirectResult.cs со специальной реализацией ActionResult, показанной в примере ниже:

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 Framework связано с возможностью исследовать внутреннюю работу чего угодно. Класс CustomRedirectResult намного проще своего эквивалента в MVC, но его вполне достаточно для целей этой статьи.

При создании экземпляра класса RedirectResult мы передаем URL, на который должен быть перенаправлен пользователь. Метод ExecuteResult(), который будет выполнен инфраструктурой MVC Framework по завершении метода действия, получает объект Response для запроса через объект ControllerContext, предоставляемый инфраструктурой, и вызывает либо метод RedirectPermanent(), либо метод Redirect() (это в точности отражает то, что делалось внутри низкоуровневой реализации IController в примере ранее в статье).

Использование класса CustomRedirectResult проиллюстрировано в примере ниже, в котором представлены изменения, которые внесены в контроллер Derived:

// ...
using ControllersAndActions.Infrastructure;

namespace ControllersAndActions.Controllers
{
    public class DerivedController : Controller
    {
        public ActionResult Index()
        {
            // ...
        }

        public ActionResult ProduceOutput()
        {
            if (Server.MachineName == "MyMachineName")
                return new CustomRedirectResult { Url = "/Basic/Index" };
            else
            {
                Response.Write("Контроллер: Derived, Метод действия: ProduceOutput");
                return null;
            }
        } 
    }
}

Обратите внимание, что мы были вынуждены изменить результат метода действия для возвращения ActionResult. Мы возвращаем null, если не хотим, чтобы инфраструктура MVC Framework предпринимала что-либо, когда наш метод действия был выполнен, что и делалось в случае невозвращения экземпляра CustomRedirectResult.

Модульное тестирование контроллеров и действий

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

  1. Тестировать действия и контроллеры можно за пределами веб-сервера. Доступ к объектам контекста осуществляется через их базовые классы (такие как HttpRequestBase), что легко поддается имитации.

  2. Для тестирования результатов метода действия проводить разбор HTML-разметки не понадобится. Чтобы удостовериться в получении ожидаемых результатов, можно проинспектировать возвращаемый объект ActionResult.

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

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

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

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

// ...
public ActionResult ProduceOutput()
{
     return new RedirectResult("/Basic/Index");
} 
// ...

Из метода действия был удален условный оператор, а это означает, что после запуска приложения и перехода на URL вида /Derived/ProduceOutput браузер будет перенаправлен на URL вида /Basic/Index. Чтобы упростить код метода действия, класс Controller включает удобные методы для генерации различных видов объектов ActionResult. Таким образом, к примеру, мы можем получить тот же эффект, что и в примере выше, возвратив результат метода Redirect():

// ...
public ActionResult ProduceOutput()
{
     return Redirect("/Basic/Index");
} 
// ...

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

В инфраструктуре MVC Framework определено множество встроенных типов результатов действий, которые описаны в таблице ниже:

Встроенные типы ActionResult
Тип Описание Вспомогательные методы класса Controller
ViewResult

Визуализирует указанный или стандартный шаблон представления

View()
PartialViewResult

Визуализирует указанный или стандартный шаблон частичного представления

PartialView()
RedirectToRouteResult

Выдает перенаправление HTTP 301 или 302 на метод действия или указанную запись маршрута, генерируя URL согласно конфигурации маршрутизации

RedirectToAction()
RedirectToActionPermanent()
RedirectToRoute()
RedirectToRoutePermanent()
RedirectResult

Выдает перенаправление HTTP 301 или 302 на заданный URL

Redirect()
RedirectPermanent()
ContentResult

Возвращает браузеру неформатированные текстовые данные, дополнительно устанавливая заголовок content-type

Content()
FileResult

Передает двоичные данные (такие как файл на диске или байтовый массив в памяти) напрямую в браузер

File()
JsonResult

Сериализирует объект .NET в формат JSON и отправляет его в качестве ответа. Ответы подобного вида более часто генерируются при использовании средств Web API и AJAX

Json()
JavaScriptResult

Отправляет фрагмент исходного кода JavaScript, который должен быть выполнен браузером

JavaScript()
HttpUnauthorizedResult

Устанавливает код состояния ответа HTTP в 401 (означает "не авторизован"), который заставляет действующий механизм аутентификации (аутентификация с помощью форм или аутентификация Windows) предложить посетителю войти

Нет
HttpNotFoundResult

Возвращает ошибку HTTP с кодом 404 - Not found (не найдено)

HttpNotFound()
HttpStatusCodeResult

Возвращает указанный код HTTP

Нет
EmptyResult

Ничего не делает

Нет

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

Пройди тесты