Выражения Razor

78

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

Роли, исполняемые методом действия и представлением
Компонент Что делает Что не делает
Метод действия

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

Не передает представлению сформатированные данные

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

Использует объект модели представления для отображения содержимого пользователю

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

Чтобы извлечь максимум из MVC Framework, необходимо обеспечить отделение разных частей приложения друг от друга. Вы увидите, что механизм Razor позволяет делать многое, включая использование операторов C#. Однако вы не должны применять Razor для выполнения бизнес-логики или манипулирования объектами моделей предметной области.

Кроме того, вы не должны форматировать данные, которые метод действия передает представлению. Вместо этого позвольте представлению вычислить данные, которые ему необходимо отобразить. Простой пример этого был приведен в предыдущей статье. Мы определили метод действия по имени NameAndPrice(), который отображает значения свойств Name и Price объекта Product. Несмотря на то что известно, значения каких свойств должны отображаться, модели представления передается полный объект Product:

public ActionResult NameAndPrice() {
    return View(myProduct);
}

Затем внутри представления используется Razor-выражение @Model для получения значений интересующих свойств:

Название продукта - @Model.Name, он стоит $@Model.Price

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

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

Вставка значений данных

Самое простое, что можно делать с помощью выражения Razor - вставлять значения данных в разметку. Это осуществляется с использованием выражения @Model, позволяющего ссылаться на свойства и методы, которые определены объектом модели представления, или выражения @ViewBag для ссылки на свойства, определенные динамически с помощью объекта ViewBag.

Вы уже видели примеры обоих выражений, но для полноты мы добавили в контроллер Home новый метод действия по имени DemoExpressions, который передает данные представлению с применением объекта модели и объекта ViewBag. Определение этого нового метода действия приведено в примере ниже:

using System.Web.Mvc;
using Razor.Models;

namespace Razor.Controllers {

    public class HomeController : Controller {
        
        // ...

        public ActionResult DemoExpression() {

            ViewBag.ProductCount = 1;
            ViewBag.ExpressShip = true;
            ViewBag.ApplyDiscount = false;
            ViewBag.Supplier = null;

            return View(myProduct);
        }
    }
}

Для демонстрации этих базовых типов выражений мы создали строго типизированное представление в файле по имени DemoExpression.cshtml, расположенном в папке Views/Home, содержимое которого показано в примере ниже:

@model Razor.Models.Product

@{
    ViewBag.Title = "DemoExpression";
    Layout = "~/Views/_BasicLayout.cshtml";
}

<table>
    <thead>
        <tr><th>Свойство</th><th>Значение</th></tr>
    </thead>
    <tbody>
        <tr><td>Название</td><td>@Model.Name</td></tr>
        <tr><td>Цена</td><td>@Model.Price</td></tr>
        <tr>
            <td>В наличии</td>
            <td>@ViewBag.ProductCount</td>
        </tr>
    </tbody>
</table>

В этом примере создается простая HTML-таблица, ячейки которой заполняются свойствами из объекта модели и объекта ViewBag. На рисунке ниже показан результат запуска приложения и перехода по URL вида /Home/DemoExpression. Он лишь подтверждает работу базовых выражений Razor, которые применялись в примерах до сих пор.

Использование базовых выражений Razor для вставки значений данных в HTML-разметку

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

Установка значений атрибутов

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

@model Razor.Models.Product

@{
    ViewBag.Title = "DemoExpression";
    Layout = "~/Views/_BasicLayout.cshtml";
}

<table>
    <thead>
        <tr><th>Свойство</th><th>Значение</th></tr>
    </thead>
    <tbody>
        <tr><td>Название</td><td>@Model.Name</td></tr>
        <tr><td>Цена</td><td>@Model.Price</td></tr>
        <tr>
            <td>В наличии</td>
            <td>@ViewBag.ProductCount</td>
        </tr>
    </tbody>
</table>

<div data-discount="@ViewBag.ApplyDiscount" data-express="@ViewBag.ExpressShip"
     data-suplier="@ViewBag.Supplier">
    Контейнер с data-атрибутами.
</div>

Скидка: <input type="checkbox" checked="@ViewBag.ApplyDiscount" />
Быстрая доставка: <input type="checkbox" checked="@ViewBag.ExpressShip" />
Доставка: <input type="checkbox" checked="@ViewBag.Supplier" />

Выражения Razor используются для установки значений некоторых атрибутов данных в элементе div.

Атрибуты данных, имена которых начинаются с data-, в течение многих лет были неформальным способом создания специальных атрибутов, а теперь они сделаны частью формального стандарта HTML5. Для установки значений этих атрибутов применяются свойства ApplyDiscount, ExpressShip и Supplier объекта ViewBag.

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

<div data-discount="False" data-express="True" data-supplier="">
   Контейнер с data-атрибутами.
</div>

Значения False и True соответствуют булевским значениям свойств в объекте ViewBag, но механизм Razor сделал кое-что удобное для свойства со значением null, визуализировав для него пустую строку.

Вторая часть разметки, добавленной к представлению, более интересна. В ней определен набор флажков, атрибуты checked которых устанавливаются в те же самые свойства объекта ViewBag, что и атрибуты данных. Сгенерированная Razor разметка HTML выглядит следующим образом:

Скидка: <input type="checkbox" />
Быстрая доставка: <input type="checkbox" checked="checked" />
Доставка: <input type="checkbox" />

Механизм визуализации Razor осведомлен о принципе использования таких атрибутов, как checked, когда конфигурацию элемента изменяет присутствие самого атрибута, а не его значение (в спецификации HTML это называется булевскими атрибутами). Если бы механизм Razor вставил False, null или пустую строку в качестве значения атрибута checked, то отображаемый браузером флажок оказался бы отмеченным. Вместо этого в случае значения false или null механизм Razor полностью удаляет атрибут из элемента, обеспечивая согласованный с данными представления результат:

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

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

Механизм Razor способен обрабатывать условные операторы, а это означает возможность настройки вывода из представления на основе значений данных представления. Такой прием является наиболее важной частью Razor и позволяет создавать сложные и изменчивые компоновки, которые по-прежнему остаются простыми в понимании и сопровождении. В примере ниже приведено обновленное содержимое файла представления DemoExpreseion.cshtml, включающее условный оператор:

@model Razor.Models.Product

@{
    ViewBag.Title = "DemoExpression";
    Layout = "~/Views/_BasicLayout.cshtml";
}

<table>
    <thead>
        <tr><th>Свойство</th><th>Значение</th></tr>
    </thead>
    <tbody>
        <tr><td>Название</td><td>@Model.Name</td></tr>
        <tr><td>Цена</td><td>@Model.Price</td></tr>
        <tr>
            <td>В наличии</td>
            <td>
                @switch ((int)ViewBag.ProductCount) {
                    case 0:
                        @:Нет в наличии
                        break;
                    case 1:
                        <b>Мало осталось (@ViewBag.ProductCount)</b>
                        break;
                  default:
                        @ViewBag.ProductCount
                        break;
                }
            </td>
        </tr>
    </tbody>
</table>

Чтобы начать условный оператор, перед условным ключевым словом C# (в этом примере switch) помещается символ @. Блок кода завершается закрывающей фигурной скобкой "}", как и в случае обычного блока кода C#.

Обратите внимание, что для использования внутри оператора switch значение свойства ViewBag.ProductCount должно быть приведено к типу int. Причина в том, что Razor-выражение switch не может оценивать динамическое свойство - требуется приведение к специфичному типу, чтобы было известно, как выполнять сравнения.

Внутри блока кода Razor в вывод представления можно включать HTML-элементы и значения данных, просто определяя HTML-разметку и выражения Razor, например:

<b>Мало осталось (@ViewBag.ProductCount)</b>

Или так:

@ViewBag.ProductCount

Помещать элементы или выражения в кавычки либо отмечать их каким-то специальным образом не требуется - механизм Razor будет интерпретировать это как вывод, который должен быть обработан. Тем не менее, если нужно вставить в представление литеральный текст, когда он не содержится в HTML-элементе, об этом понадобится сообщить Razor, добавив к строке префикс, как показано ниже:

@:Нет в наличии

Символы "@:" предотвращают обработку механизмом Razor этой строки как оператора C#, что является стандартным поведением в отношении текста. Результат выполнения этого условного оператора можно видеть на рисунке ниже:

Использование оператора switch в представлении Razor

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

@model Razor.Models.Product

@{
    ViewBag.Title = "DemoExpression";
    Layout = "~/Views/_BasicLayout.cshtml";
}

<table>
    <thead>
        <tr><th>Свойство</th><th>Значение</th></tr>
    </thead>
    <tbody>
        <tr><td>Название</td><td>@Model.Name</td></tr>
        <tr><td>Цена</td><td>@Model.Price</td></tr>
        <tr>
            <td>В наличии</td>
            <td>
                @if (ViewBag.ProductCount == 0) {
                    @:нет
                } else if (ViewBag.ProductCount == 1) {
                    <b>Мало осталось (@ViewBag.ProductCount)</b>
                } else {
                    @ViewBag.ProductCount
                }
            </td>
        </tr>
    </tbody>
</table>

Этот условный оператор выдает те же самые результаты, что и оператор switch, но мы просто хотели продемонстрировать применение условных операторов C# в представлениях Razor.

Проход по содержимому массивов и коллекций

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

using System.Web.Mvc;
using Razor.Models;

namespace Razor.Controllers {

    public class HomeController : Controller {

        // ...

        public ActionResult DemoArray() {
            Product[] array = {
                new Product {Name = "Каяк", Price = 275M},
                new Product {Name = "Спасательный жилет", Price = 48.95M},
                new Product {Name = "Футбольной мяч", Price = 19.50M},
                new Product {Name = "Угловой флажок", Price = 34.95M}
            };
            return View(array);
        }
    }
}

Этот метод действия создает объект Product[], который содержит простые значения данных, и передает его методу View для визуализации с использованием стандартного представления. Средство формирования шаблонов Visual Studio не позволяет указывать массив в качестве типа модели. (Причина не известна, т.к. сам механизм Razor благополучно поддерживает массивы.)

Наилучший подход к созданию представления для метода действия, который передает массив, предусматривает создание представления без модели и затем ручное добавление выражения @model после того, как файл сгенерирован. В примере ниже показано содержимое файла DemoArray.cshtml, созданного в папке Views/Home и соответствующим образом отредактированного:

@model Razor.Models.Product[]

@{
    ViewBag.Title = "DemoArray";
    Layout = "~/Views/_BasicLayout.cshtml";
}

@if (Model.Length > 0) {
    <table>
        <thead><tr><th>Продукт</th><th>Цена</th></tr></thead>
        <tbody>
            @foreach (Razor.Models.Product p in Model) {
                <tr>
                    <td>@p.Name</td>
                    <td>$@p.Price</td>
                </tr>
            }
        </tbody>
    </table>
} else {
    <h2>Нет данных о товарах</h2>
}

Здесь с помощью оператора @if содержимое варьируется в зависимости от длины массива, который получается как модель представления, а посредством выражения @foreach выполняется проход по содержимому массива с генерацией строки HTML-таблицы для каждого элемента массива. Как видите, эти выражения соответствуют своим аналогам из C#. Кроме того, в цикле foreach создана локальная переменная по имени p, на свойства которой производится ссылка с использованием выражений Razor вида @p.Name и @р.Price.

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

Генерация элементов с применением оператора foreach

Работа с пространствами имен

В последнем примере вы наверняка заметили, что для ссылки на Product в цикле foreach используется полностью определенное имя:

@foreach (Razor.Models.Product p in Model)

Это может стать утомительным в сложных представлениях, содержащих много ссылок на модель представления и другие классы, Привести в порядок представления можно за счет применения выражения @using, чтобы обеспечить для представления контекст определенного пространства имен, как это делается для обычного класса C#. В примере ниже демонстрируется применение выражении @using:

@using Razor.Models
@model Product[]

@{
    ViewBag.Title = "DemoArray";
    Layout = "~/Views/_BasicLayout.cshtml";
}

@if (Model.Length > 0) {
    <table>
        <thead><tr><th>Продукт</th><th>Цена</th></tr></thead>
        <tbody>
            @foreach (Product p in Model) {
                <tr>
                    <td>@p.Name</td>
                    <td>$@p.Price</td>
                </tr>
            }
        </tbody>
    </table>
} else {
    <h2>Нет данных о товарах</h2>
}

Представление может содержать множество выражений @using. В показанном выше коде выражение @using используется для импорта пространства имен Razor.Models, что позволяет не указывать пространство имен в выражении @model и внутри цикла foreach.

Пройди тесты
Лучший чат для C# программистов