Генерация HTML-разметки

189

В предыдущей статье мы кратко рассмотрели классы результатов действий, позволяющих сообщать инфраструктуре ASP.NET MVC что именно нужно генерировать из методов действий контроллера (HTML-разметку, перенаправление, HTTP-ошибки и т.д.) В этой и последующих статьях мы рассмотрим эти классы более подробно.

Наиболее общим видом ответа из метода действия является генерация HTML-разметки и ее отправка браузеру. Чтобы продемонстрировать визуализацию представлений, в проект был добавлен контроллер по имени Example. Содержимое файла класса ExampleController.cs приведено в примере ниже:

using System.Web.Mvc;

namespace ControllersAndActions.Controllers
{
    public class ExampleController : Controller
    {
        public ViewResult Index()
        {
            return View("Homepage");
        }
    }
}

Когда используется система результатов действий, представление, которое инфраструктура MVC Framework должна визуализировать, указывается с применением экземпляра класса ViewResult. Проще всего это сделать с помощью вызова метода View() контроллера, передав ему представление в качестве аргумента. В примере метод View() вызывается с аргументом Homepage, который указывает на необходимость использования представления HomePage.cshtml.

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

Когда инфраструктура MVC Framework вызывает метод ExecuteResult() объекта класса ViewResult, начинается поиск указанного вами представления. Если в проекте применяются области, инфраструктура будет просматривать следующие местоположения:

/Areas/<ИмяОбласти>/Views/<ИмяКонтроллера>/<ИмяПредставления>.aspx

/Areas/<ИмяОбласти>/Views/<ИмяКонтроллера>/<ИмяПредставления>.ascx

/Areas/<ИмяОбласти>/Views/Shared/<ИмяПредставления>.aspx

/Areas/<ИмяОбласти>/Views/Shared/<ИмяПредставления>.ascx

/Areas/<ИмяОбласти>/Views/<ИмяКонтроллера>/<ИмяПредставления>.cshtml

/Areas/<ИмяОбласти>/Views/<ИмяКонтроллера>/<ИмяПредставления>.vbhtml

/Areas/<ИмяОбласти>/Views/Shared/ <ИмяПредставления>.cshtml

/Areas/<ИмяОбласти>/Views/Shared/<ИмяПредставления>.vbhtml

В этом списке видно, что инфраструктура ищет представления, которые были созданы для унаследованного механизма визуализации ASPX (файловые расширения .aspx и .ascx), несмотря на то, что MVC Framework использует механизм Razor. Цель заключается в сохранении совместимости с ранними версиями MVC Framework, в которых применялись средства визуализации из ASP.NET Web Forms.

Инфраструктура также ищет шаблоны Razor на языках C# и Visual Basic .NET. (Файлы *.cshtml содержат код C#, а файлы *.vbhtml - код Visual Basic. Синтаксис Razor в этих файлах одинаков, но фрагменты кода написаны на разных языках.) Инфраструктура MVC Framework проверяет существование каждого из указанных файлов по очереди. Как только файл представления обнаруживается, он используется для визуализации результата, возвращенного методом действия.

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

/Views/<ИмяКонтроллера>/<ИмяПредставления>.aspx

/Views/<ИмяКонтроллера>/<ИмяПредставления>.ascx

/Views/Shared/<ИмяПредставления>.aspx

/Views/Shared/<ИмяПредставления>.ascx

/Views/<ИмяКонтроллера>/<ИмяПредставления>.cshtml

/Views/<ИмяКонтроллера>/<ИмяПредставления>.vbhtml

/Views/Shared/<ИмяПредставления>.cshtml

/Views/Shared/<ИмяПредставлеиия>.vbhtml

Опять-таки, когда MVC Framework обнаруживает файл, поиск останавливается а найденное представление применяется для визуализации ответа клиенту. В нашем примере приложения области отсутствуют, поэтому первым местоположением, которое будет просматривать инфраструктура, является /Views/Example/Index.aspx. Обратите внимание, что часть Controller в имени класса опущена, так что создание ViewResult в ExampleController приводит к поиску в папке по имени Example.

Модульное тестирование: визуализация представления

Чтобы протестировать представление, которое визуализирует метод действия, можно проинспектировать возвращаемый им объект ViewResult. Это не совсем то же самое (в конце концов, вы не следуете процессу посредством проверки финальной HTML-разметки, которая была сгенерирована), но данный прием очень близок к реальности, т.к. позволяет удостовериться в корректной работе системы представлений MVC Framework.

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

// ...
public ViewResult Index()
{
    return View("Homepage");
}
// ...

Для определения того, какое представление было выбрано, необходимо прочитать свойство viewName объекта ViewResult, как показано в следующем тестовом методе:

using System;
using System.Web.Mvc;
using ControllersAndActions.Controllers;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace ControllersAndActions.Tests
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void ControllerTest()
        {
            // Организация - создание контроллера
            ExampleController controller = new ExampleController();

            // Действие - вызов метода действия
            ViewResult result = controller.Index();

            // Утверждение - проверка результата
            Assert.AreEqual("Homepage", result.ViewName);
        } 
    }
}

Небольшое изменение появляется при тестировании метода действия, который выбирает стандартное представление:

// ...
public ViewResult Index()
{
    return View();
}
// ...

В таких ситуациях для имени представления должна приниматься пустая строка (""):

// ...
// Утверждение - проверка результата
Assert.AreEqual("", result.ViewName);

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

Последовательность папок, в которых MVC Framework ищет представление, является примером соглашения по конфигурации. Регистрировать файлы представлений с помощью инфраструктуры не нужно. Они просто должны быть помещены в одно из известных местоположений, и инфраструктура самостоятельно найдет их. Мы можем доследовать соглашению еще дальше, опустив имя визуализируемого представления при вызове метода View(), как показано в примере ниже:

// ...
public ViewResult Index()
{
    return View();
}
// ...

Инфраструктура MVC Framework предполагает, что мы хотим визуализировать представление, которое имеет то же самое имя, что и метод действия. Это означает, что вызов метода View() в примере ниже инициирует поиск представления по имени Index.

Инфраструктура MVC Framework в действительности получает имя метода действия из значения RouteData.Values["action"], которое является частью системы маршрутизации. Имя метода действия и значение маршрутизации будут совпадать, когда используются встроенные классы маршрутизации, но это может быть не так в случае реализации специальных классов маршрутизации, которые не следуют соглашениям MVC Framework.

Доступно несколько перегруженных версий метода View(). Они соответствуют установке разнообразных свойств созданного объекта ViewResult. Например, ниже показано, как переопределить компоновку, применяемую представлением, явно указав альтернативное имя:

// ...
public ViewResult Index()
{
    return View("Index", "_AlternateLayout");
}
// ...

Указание представления по его пути

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

// ...
public ViewResult Index()
{
    return View("~/Views/MyFolder/Index.cshtml");
}
// ...

При таком указании представления путь должен начинаться с "/" или "~/" и включать расширение имени файла (наподобие .cshtml для представлений Razor, содержащих код C#).

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

Передача данных из метода действия в представление

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

Предоставление объекта модели представления

Отправить объект представлению можно, передав его в качестве параметра методу View(), как показано в примере ниже:

using System;
using System.Web.Mvc;

namespace ControllersAndActions.Controllers
{
    public class ExampleController : Controller
    {
        public ViewResult Index()
        {
            DateTime date = DateTime.Now;
            return View(date);
        }
    }
}

Объект DateTime передается как модель представления, а доступ к нему в представлении осуществляется с помощью ключевого слова Model синтаксиса Razor. Для демонстрации использования ключевого слова Model в папку Views/Example добавлено представление по имени Index.cshtml с содержимым, приведенным в примере ниже:

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

День недели: @(((DateTime)Model).DayOfWeek)

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

@model DateTime
@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

День недели: @Model.DayOfWeek

Тип модели представления указывается с применением ключевого слова model синтаксиса Razor. Обратите внимание на использование буквы "m" нижнего регистра при указании типа модели и буквы "M" верхнего регистре при чтении значения. Строгая типизация не только помогает справиться с представлением, но также позволяет Visual Studio поддерживать средство IntelliSense:

Поддержка средства IntelliSense для строго типизированных представлений

Модульное тестирование: объекты модели представления

Получить доступ к объекту модели представления, переданному из метода действия в представление, можно с помощью свойства ViewResult.ViewData.Model. Ниже приведен код тестового метода для метода действия Index. В тестовом методе с помощью метода Assert.IsInstanceOfType() выполняется проверка того, что объект модели представления является экземпляром типа DateTime:

// ...
[TestMethod]
public void ViewSelectionTest()
{
    // Организация - создание контроллера
    ExampleController controller = new ExampleController();

    // Действие - вызов метода действия
    ViewResult result = controller.Index();

    // Утверждение - проверка результата
    Assert.AreEqual("", result.ViewName);
    Assert.IsInstanceOfType(result.ViewData.Model, typeof(System.DateTime));
}
// ...

Передача данных с помощью ViewBag

Объект ViewBag позволяет определять произвольные свойства в динамическом объекте C# и обращаться к ним в представлении. Динамический объект доступен через свойство Controller.ViewBag, как показано в примере ниже:

using System;
using System.Web.Mvc;

namespace ControllersAndActions.Controllers
{
    public class ExampleController : Controller
    {
        public ViewResult Index()
        {
            ViewBag.Message = "Привет";
            ViewBag.Date = DateTime.Now;
            return View();
        }
    }
}

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

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

День недели: @(ViewBag.Date.DayOfWeek)<br />
Сообщение из контроллера: @ViewBag.Message

Преимущество использования ViewBag по сравнению с объектом модели представления связано с простотой отправки множества объектов представлению. Если ограничиться применением только моделей представлений, понадобилось бы создать новый тип с членами string и DateTime, чтобы получить такой же результат.

При работе с динамическими объектами в представлении можно вводить любую последовательность обращений к методам и свойствам, например:

...
@ViewBag.Date.DayOfWeek.Hahaha.NoExistProperty
...

Среда Visual Studio не может обеспечить поддержку IntelliSense для динамических объектов, в том числе и для ViewBag, поэтому возможные ошибки не будут распознаны вплоть до визуализации представления.

Модульное тестирование: объект ViewBag

Чтение значений из ViewBag осуществляется через свойство ViewResult.ViewBag. Следующий тестовый метод предназначен для текущего метода действия Index:

// ...
[TestMethod]
public void ControllerTest()
{
    // Организация - создание контроллера
    ExampleController controller = new ExampleController();

    // Действие - вызов метода действия
    ViewResult result = controller.Index();

    // Утверждение - проверка результата
    Assert.AreEqual("", result.ViewName);
    Assert.AreEqual("Привет", result.ViewBag.Message);
}
// ...
Пройди тесты
Лучший чат для C# программистов