Мастер-страницы или layout позволяют задать единый шаблон для представлений и применяются для создания единообразного, унифицированного вида сайта. По сути мастер-страницы - это те же самые представления, которе могут включать в себя другие представления. Например, можно определить на мастер-странице общие для всех остальных представлений меню, а также подключить общие стили и скрипты. В итоге нам не придется на каждом отдельном представлении прописывать путь к файлам стилей, а потом при необходимости его изменять. А специальные теги позволяют вставлять в определенное место на мастер-страницах другие представления.
Например, возьмем простейший проект ASP.NET Core по типу ASP.NET Core Empty.
Сначала в файле Program.cs подключим функционал контроллеров и представлений:
var builder = WebApplication.CreateBuilder(args); // добавляем поддержку контроллеров с представлениями builder.Services.AddControllersWithViews(); var app = builder.Build(); // устанавливаем сопоставление маршрутов с контроллерами app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.Run();
Пусть в проекте будет папка Controllers, где будет располагаться контроллер HomeController с двумя методами:
using Microsoft.AspNetCore.Mvc; namespace MvcApp.Controllers { public class HomeController : Controller { public IActionResult Index() => View(); public IActionResult About() => View(); } }
Оба действия контроллера возвращают ViewResult. Поэтому нам надо добавить для них представления. Но в начале определим мастер-страницу layout.
Создадим в проекте папку Views, а в ней создадим папку Shared. Далее добавим в каталог Views/Shared новое представление, которое назовем _Layout.cshtml. Для добавления мастер-страниц в Visual Studio можно использовать шаблон файла Razor Layout:
После добавления файла _Layout.cshtml изменим его код следующим образом:
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>METANIT.COM | @ViewBag.Title</title> </head> <body> <h2>@ViewBag.Title</h2> <div><a href="/Home/Index">Home</a> | <a href="/Home/About">About</a></div> <div> @RenderBody() </div> </body> </html>
Код мастер-страницы напоминает полноценную веб-страницу: здесь присутствуют основные теги <html>
, <head>
,
<body>
и так далее. И также здесь могут использоваться конструкции Razor. Фактически это то же самое представление. Например, через выраженеи
@ViewBag.Title
из каждого отдельного представления будет передаваться значение для заголовка веб-страницы.
Главное же отличие от обычных представлений состоит в использовании метода @RenderBody(), который является плейсхолдером и на место которого потом будут подставляться другие представления, использующие данную мастер-страницу. В итоге мы сможем легко установить для всех представлений веб-приложения единообразный стиль оформления.
Теперь добавим в папку Views новую папку Home для хранения представлений контроллера HomeController. Далее в эту папку поместим новое представление Index.cshtml (для метода Index):
@{ ViewBag.Title = "Index"; Layout = "/Views/Shared/_Layout.cshtml"; } <h3>Index Content</h3>
Здесь задается значение ViewBag.Title
, которое применяется на мастер-странице для выводпа заголовка.
Кроме того, с помощью свойства Layout устанавливается используемая мастер-страница layout. В данном случае это файл по пути /Views/Shared/_Layout.cshtml
Также добавим в папку Views/Home новое представление About.cshtml (для метода About):
@{ ViewBag.Title = "About"; Layout = "/Views/Shared/_Layout.cshtml"; } <h3>About Content</h3>
Оно выглядит аналогично представлению Index.cshtml.
В итоге весь проект будет выглядеть следующим образом:
Запустим проект и в браузере при обращении к обоим действиям контроллера - Index и About мы лицезреем в браузере единообразую веб-страницу:
При необходимости мы можем определять разные мастер-страницы, например, отдельные мастер-страницы для разных контроллеров, и таким образом подключать в конкретные представления.
Хотя выше приведенный код вполне успешно работает, у нас есть одна проблема - мы сталкиваемся с необходимостью в каждом представлении явным образом прописывать, какую мастер-страницу layout будет применять представление. Чтобы упростить данное действие, можно применять файлы _ViewStart.cshtml
Итак, добавим в папку Views новое представление, которое назовем _ViewStart.cshtml.
Код этого файла добавляется в самое начало кода преставлений при их запуске. При этом файлы представлений, к которым применяется _ViewStart.cshtml, должны находиться с этим файлов в одном каталоге
Определим в файле _ViewStart.cshtml следующий код:
@{ Layout = "_Layout"; }
В каждом представлении через синтаксис Razor доступно свойство Layout, которое хранит ссылку на мастер-страницу. Здесь в качестве мастер страницы устанавливается файл _Layout.cshtml. При этом расширение можно не использовать.
Когда будет происходить рендеринг представления, то система будет искать мастер страницу _Layout по следующим путям:
/Views/[Название_контроллера]/_Layout.cshtml /Views/Shared/_Layout.cshtml
Если в обеих папках: и в /Views/[Название_контроллера], и в /Views/Shared/ имеется файл с одинаковым
именем, например, _Layout.cshtml
, то к представлению применяется файл, который находится с ним в одной папке как более приоритетный.
То есть таким образом мы можем определить для представлений каждого отдельного контроллера или представлений, которые находятся в одной папке, свою отдельную мастер-страницу.
После определения этого файла мы можем удалить из представлений Index.cshtml и About.cshtml подключение мастер-страницы. Например, представление Index.cshtml:
@{ ViewBag.Title = "Index"; } <h3>Index Content</h3>
Если вдруг мы хотим глобально по всему проекту поменять мастер-страницу на другой файл, который расположен в какой-то другой папке, например, в корне каталога Views, то нам надо использовать полный путь к файлу в _ViewStart.cshtml:
@{ Layout = "~/Views/_Layout.cshtml"; }
Код из _ViewStart.cshtml выполняется до любого кода в представлении. И чтобы переопределить мастер-страницу, в представлении доcтаточно установить свойство Layout.
Естественно также мы можем переопределить мастер-страницу в каждом отдельном представлении с помощью свойства Layout.
@{ ViewBag.Title = "Home Page"; Layout = "~/Views/Shared/_Layout.cshtml"; } <h2>Представление Index.cshtml</h2>
Мы можем вообще не использовать мастер-страницу, тогда в представлении свойству Layout
надо присвоить значение null
:
@{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Home Page</title> </head> <body> <h2>Представление Index.cshtml</h2> </body> </html>
Кроме метода RenderBody()
, который вставляет освновное содержимое представлений, мастер-страниц может также использовать специальный
метод RenderSection()
для вставки секций. Мастер-страница может иметь несколько секций, куда представления могут поместить свое содержимое.
Например, добавим к мастер-странице _Layout.cshtml секцию с именем "footer":
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>METANIT.COM | @ViewBag.Title</title> </head> <body> <h2>@ViewBag.Title</h2> <div><a href="/Home/Index">Home</a> | <a href="/Home/About">About</a></div> <div> @RenderBody() </div> <footer>@RenderSection("Footer")</footer> </body> </html>
Теперь при запуске предыдущего представления Index мы получим ошибку, так как секция Footer не определена. По умолчанию представление должно
передавать содержание для каждой секции мастер-страницы. Поэтому добавим вниз представления Index секцию footer. Это мы можем сделать с помощью
выражения @section
:
@{ ViewBag.Title = "Index"; } </h3>Index Content</h3> @section Footer { Copyright© Metanit.com, @DateTime.Now.Year. All rights reserved }
Но при таком подходе, если у нас есть куча представлений, и мы вдруг захотели определить новую секцию на мастер-странице, нам придется изменить все имеющиеся представления, что не очень удобно. В этом случае мы можем воспользоваться одним из вариантов гибкой настройки секций.
Первый вариант заключается в использовании перегруженной версии метода RenderSection, которая позволяет указать, что
данную секцию не обязательно определять в представлении. Чтобы отметить секцию Footer
в качестве необязательной, надо передать в метод
в качестве второго параметра значение false
:
<footer>@RenderSection("Footer", false)</footer>
Второй вариант позволяет задать содержание секции по умолчанию, если данная секция не определена в представлении:
<footer> @if (IsSectionDefined("Footer")) { @RenderSection("Footer") } else { <span>Содержание элемента footer по умолчанию.</span> } </footer>