Разделы компоновки

108

Механизм Razor поддерживает концепцию разделов, которые позволяют организовывать области содержимого внутри компоновки. Разделы Razor предоставляют больший контроль над тем, какие части представления вставляются в компоновку, и куда они помещаются. Для демонстрации работы разделов файл /Views/Home/Index.cshtml проекта, который мы начали в предыдущей статье, отредактирован, как показано в примере ниже:

@model string[]
@{
    ViewBag.Title = "Index";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

@section Header {
    <div class="view">
        @foreach (string str in new[] { "Home", "List", "Edit" })
        {
            @Html.ActionLink(str, str, null, new { style = "margin: 5px" })
        }
    </div>
}

<div class="view">
    <h2>Названия фруктов</h2>

    @foreach (string fruit in Model)
    {
        <br /><span><b>@fruit</b></span>
    }
</div>

@section Footer {
    <div class="view">
        Это футер
    </div>
}

Разделы определяются с помощью Razor-дескриптора @section, за которым следует имя раздела. В этом примере созданы разделы с именами Header и Footer. Раздел содержит обычную смесь разметки HTML и дескрипторов Razor. Разделы определяются в представлении, но применяются в компоновке посредством вспомогательного метода RenderSection(). Чтобы продемонстрировать это в работе, был создан файл /Views/Shared/_Layout.cshtml, содержимое которого приведено в примере ниже:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title</title>
    <style>
        div.layout { background-color: lightblue; }
        div.view { border: 1px solid orange; margin: 10px 0; }
    </style>
</head>
<body>
    @RenderSection("Header")
    <div class="layout">
        Этот текст находится в компоновке
    </div>
    @RenderSection("Body")
    <div class="layout">
        Этот текст находится в компоновке
    </div>
    @RenderSection("Footer")
    <div class="layout">
        Этот текст находится в компоновке
    </div>
</body>
</html>

Местоположения для специального механизма визуализации по-прежнему используются, но в файле Index.cshtml представление было указано явно, а это означает, что компоновка будет извлекаться из папки /Views/Shared, хотя разделяемые представления находятся в папке /Views/Common.

Когда Razor осуществляет разбор компоновки, вызов вспомогательного метода RenderSection() заменяется содержимым раздела в представлении с указанным именем. Части представления, которые не содержатся в каком-либо разделе, вставляются в компоновку с использованием вспомогательного метода RenderBody().

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

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

В представлении можно определять только разделы, на которые имеются ссылки в компоновке. При попытке определить в представлении разделы, для которых отсутствуют соответствующие вызовы вспомогательного метода @RenderSection() в компоновке, MVC Framework сгенерирует исключение.

Смешивание разделов с остальной частью представления обычно не делается. По соглашению все разделы должны определяться либо в начале, либо в конце представления, чтобы упростить выяснение того, какие области содержимого будут трактоваться как разделы, а что будет захвачено вспомогательным методом RenderBody(). Более предпочтительный подход предполагает определение представления полностью в терминах разделов, включая раздел для тела (Body), как показано в примере ниже:

...
@section Body {
    <div class="view">
        <h2>Названия фруктов</h2>
        @foreach (string fruit in Model)
        {
            <br /><span><b>@fruit</b></span>
        }
    </div>
}
...

При таком подходе получаются более чистые представления и уменьшаются шансы захвата излишнего содержимого методом RenderBody(). Для применения этого подхода необходимо заменить вызов вспомогательного метода RenderBody() вызовом RenderSection("Body") в компоновке _Layout.cshtml, как показано в примере ниже:

...
@RenderSection("Body")
...

Проверка существования разделов

Внутри компоновки можно выполнить проверку, определен ли в представлении конкретный раздел. Это полезно, когда нужно обеспечить наличие стандартного содержимого для раздела, для которого в представлении ничего не задано. Содержимое файла _Layout.cshtml было изменено для добавления проверки, определен ли раздел Footer:

...
@if (IsSectionDefined("Footer"))
{
    @RenderSection("Footer")
} else
{
    <h4>Это футер по умолчанию</h4>
}
...

Вспомогательный метод IsSectionDefined() принимает имя проверяемого раздела и возвращает true, если он определен в визуализируемом представлении. В данном примере этот вспомогательный метод используется для выяснения необходимости в генерации стандартного содержимого, когда в представлении не определен раздел Footer.

Визуализация необязательных разделов

По умолчанию представление должно содержать все разделы, для которых имеются вызовы RenderSection() в компоновке. Если какой-то раздел отсутствует, инфраструктура MVC Framework сообщит об этом пользователю, сгенерировав исключение. Чтобы продемонстрировать это, в файл _Layout.cshtml добавляется новый вызов RenderSection() для раздела по имени Scripts. Указанный раздел добавляется в компоновку средой Visual Studio по умолчанию, когда создается проект MVC с применением шаблона MVC.

<!DOCTYPE html>
<html>
<head>
    ...
</head>
<body>
    ...
    @RenderSection("Scripts")
    <div class="layout">
        Этот текст находится в компоновке
    </div>
</body>
</html>

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

Сообщение об ошибке, отображаемое из-за отсутствия раздела

Можно воспользоваться методом IsSectionDefined(), чтобы избежать вызова RenderSection() для разделов, которые в представлении не определены, но более элегантный подход предусматривает применение необязательных разделов, для которых методу RenderSection() передается дополнительное значение false:

...
@RenderSection("Scripts", false)
...

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

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