Главная страница   /   18.3. Добавление динамического контента в представление Razor (ASP.NET MVC 4

ASP.NET MVC 4

ASP.NET MVC 4

Адам Фриман

18.3. Добавление динамического контента в представление Razor

Главная цель представлений - визуализировать компоненты доменной модели как компоненты пользовательского интерфейса. Для этого нужно уметь добавлять в представления динамический контент. Динамический контент генерируется во время выполнения и может быть разным для разных запросов. Это его отличие от статического контента, такого как HTML, который вы создаете при написании приложения и который остается одинаковым для всех запросов. Динамический контент можно добавить в представления несколькими способами, которые описаны в таблице 18-2.

Таблица 18-2: Способы добавления динамического контента в представления
Способ Используется для
Код Используется для создания небольших, независимых частей логики представления, например, операторы if или foreach. Это основной способ создания динамического контента в представлении, и на нем основаны некоторые другие подходы. Мы рассмотрели его в главе 5 и уже использовали во многих примерах в других главах.
Вспомогательные методы HTML Используются для создания одного элемента HTML или небольшой коллекции элементов, обычно на основании данных модели представления или объекта ViewData. Можно использовать встроенные вспомогательные методы MVC Framework, можно также создавать свои собственные. Вспомогательные методы HTML рассматриваются в главе 19.
Секции Используются для разбиения контента на блоки, которые будут вставлены в макет в определенных местах.
Частичные представления Используются для включения подсекции разметки в несколько представлений. Частичные представления могут содержать код, вспомогательные методы HTML и ссылки на другие частичные представления. Частичные представления не могут вызывать методы действий, поэтому их нельзя использовать для выполнения бизнес-логики.
Дочерние действия Используются для создания повторно используемых элементов управления UI и виджетов, в которых необходима бизнес-логика. Дочернее действие вызывает метод действия, визуализирует представление и внедряет результат в поток ответа.

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

Используем секции

Движок Razor поддерживает концепцию секций, которые позволяют выделять различные области в макете. Секции Razor позволяют разбивать представление на части и контролировать то, где они внедряются в макет. Чтобы продемонстрировать работу секций, мы отредактировали файл /Views/Home/Index.cshtml, как показано в листинге 18-16.

Листинг 18-16: Определяем секцию в представлении
@model string[]
@{
	ViewBag.Title = "Index";
}

@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">
	This is a list of fruit names:
	@foreach (string name in Model)
	{
		<span><b>@name</b></span>
	}
</div>

@section Footer {
	<div class="view">
		This is the footer
	</div>
}

Секции определяются с помощью тега Razor @, за которым следует имя секции. В нашем примере их две - Header и Footer. Их содержание представляет собой обычную смесь разметки HTML и тегов Razor.

Чтобы указать, где в макете должны отображаться секции, используется вспомогательный метод @RenderSection. В листинге 18-17 показаны изменения, которые мы внесли в файл ~/Views/Shared/_Layout.cshtml.

Подсказка

Движок представлений все еще использует наши пользовательские адреса поиска, что означает, что макеты находятся в папке /Views/Shared, а представления - в /Views/Common.

Листинг 18-17: Используем секции в макете
<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8" />
	<meta name="viewport" content="width=device-width" />
	<style type="text/css">
		div.layout {
			background-color: lightgray;
		}

		div.view {
			border: thin solid black;
			margin: 10px 0;
		}
	</style>
	<title>@ViewBag.Title</title>
</head>
<body>
	@RenderSection("Header")

	<div class="layout">
		This is part of the layout
	</div>

	@RenderBody()

	<div class="layout">
		This is part of the layout
	</div>

	@RenderSection("Footer")

	<div class="layout">
		This is part of the layout
	</div>
</body>
</html>

Когда Razor анализирует макет, он заменяет вспомогательный метод RenderSection на содержимое секции представления с указанным именем. Те части представления, которые не содержатся в секциях, вставляются в макет с помощью вспомогательного метода RenderBody.

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

Рисунок 18-6: Используем секции в представлении, чтобы внедрить контент в макет

Примечание

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

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

Листинг 18-18: Определяем представление с помощью секций Razor
@model string[]
@{
	ViewBag.Title = "Index";
}

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

@section Body {
	<div class="view">
		This is a list of fruit names:
		@foreach (string name in Model)
		{
			<span><b>@name</b></span>
		}
	</div>
}

@section Footer {
	<div class="view">
		This is the footer
	</div>
}

Мы считаем, что такой подход позволяет создавать более понятные представления и снижает вероятность того, что RenderBody захватит посторонний контент. Чтобы его применить, мы должны заменить вызов вспомогательного метода RenderBody на RenderSection("Body"), как показано в листинге 18-19.

Листинг 18-19: Определяем представление с помощью RenderSection("Body")
<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8" />
	<meta name="viewport" content="width=device-width" />
	<style type="text/css">
		div.layout {
			background-color: lightgray;
		}

		div.view {
			border: thin solid black;
			margin: 10px 0;
		}
	</style>
	<title>@ViewBag.Title</title>
</head>
<body>
	@RenderSection("Header")

	<div class="layout">
		This is part of the layout
	</div>

	@RenderSection("Body")

	<div class="layout">
		This is part of the layout
	</div>

	@RenderSection("Footer")

	<div class="layout">
		This is part of the layout
	</div>
</body>
</html>

Тестирование секций

Вы можете проверить, определена ли в представлении какая-либо секция из макета. Это делается для того, чтобы предоставить контент по умолчанию для этой секции, если она отсутствует в представлении и мы по каким-то причинам не хотим ее там определять. Мы изменили файл _Layout.cshtml, чтобы проверить, определена ли в нем секция Footer, как показано в листинге 18-20.

Листинг 18-20: Проверяем наличие секции Footer в представлении
@if (IsSectionDefined("Footer"))
{
	@RenderSection("Footer")
}
else
{
	<h4>This is the default footer</h4>
}

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

Визуализируем дополнительные секции

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

Листинг 18-21: Добавляем в макет вызов RenderSection, для которого нет соответствующей секции в представлении
<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8" />
	<meta name="viewport" content="width=device-width" />
	<style type="text/css">
		div.layout {
			background-color: lightgray;
		}

		div.view {
			border: thin solid black;
			margin: 10px 0;
		}
	</style>
	<title>@ViewBag.Title</title>
</head>
<body>
	@RenderSection("Header")

	<div class="layout">
		This is part of the layout
	</div>

	@RenderSection("Body")

	<div class="layout">
		This is part of the layout
	</div>

	@if (IsSectionDefined("Footer"))
	{
		@RenderSection("Footer")
	}
	else
	{
		<h4>This is the default footer</h4>
	}

	@RenderSection("scripts")

	<div class="layout">
		This is part of the layout
	</div>
</body>
</html>

Если мы запустим приложение и движок Razor попытается визуализировать макет и представление, то увидим ошибку, показанную на рисунке 18-7.

Рисунок 18-7: Ошибка, которая появляется в случае отсутствия секции

Вы можете использовать метод IsSectionDefined, чтобы избежать вызовов RenderSection к секциям, не определенным в представлении, но для этого есть более элегантный подход: передавать дополнительное значение false в метод RenderSection, как показано в листинге 18-22.

Листинг 18-22: Делаем секцию необязательной
@RenderSection("scripts", false)

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

Используем частичные представления

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

Создаем частичное представление

Для начала создадим частичное представление под названием MyPartial. Для этого кликните правой кнопкой мыши по папке /Views/Shared, выберите из контекстного меню Add - View и отметьте флажком опцию Create as Partial View, как показано на рисунке 18-8.

Рисунок 18-8: Создаем частичное представление

Сразу после создания оно будет пустым, и мы добавим в него содержимое, показанное в листинге 18-23.

Листинг 18-23: Файл MyPartial.cshtml
<div>
	This is the message from the partial view.
	@Html.ActionLink("This is a link to the Index action", "Index")
</div>

Мы хотим продемонстрировать, как можно смешивать HTML-разметку и теги Razor, поэтому мы определили в нем простое сообщение и вызов к вспомогательному методу ActionLink.

Чтобы использовать частичное представление, нужно вызвать вспомогательный метод HTML Partial из другого представления. Для демонстрации мы внесли изменения в файл ~/Views/Common/List.cshtml, как показано в листинге 18-24.

Листинг 18-24: Используем частичное представление
@{
	ViewBag.Title = "List";
	Layout = null;
}

<h3>This is the /Views/Common/List.cshtml View</h3>

@Html.Partial("MyPartial")

Имя файла представления указывается без расширения. Движок представлений будет искать указанное частичное представление по обычным адресам поиска, то есть при визуализации представления для контроллера Home - в папках /Views/Home и /Views/Shared. (Мы установили переменной Layout значение null, так что теперь нам не нужно указывать секции, которые использовались ранее в этой главе.)

Подсказка

Движок представлений Razor ищет частичные представления так же, как и обычные (в папках ~/Views/<controller> и ~/Views/Shared). Это значит, что можно создавать специализированные версии частичных представлений для конкретных контроллеров и переопределять частичные представления с одинаковыми именами в папке Shared. Хотя последняя функция может показаться странной, она очень удобна, потому что чаще всего частичные представления используются для визуализации контента в макете.

Вы можете увидеть вывод частичного представления, запустив приложение и перейдя по ссылке /Home/List, как показано на рисунке 18-9.

Рисунок 18-9: Вывод частичного представления

Подсказка

С помощью вызова вспомогательного метода ActionLink в частичном представлении мы получаем из обрабатываемого запроса информацию о контроллере. Это значит, что если мы указываем метод Index, элемент a будет относиться к контроллеру Home, так как именно этот контроллер вызывает визуализацию частичного представления. Если мы используем частичные представления в представлении, которое визуализируется другим контроллером, то ActionLink сгенерирует ссылку на этот контроллер. Мы вернемся к вспомогательным методам HTML в главе 19.

Используем строго типизированные частичные представления

Можно также создать строго типизированное частичное представление, а затем передавать в него объекты моделей представлений, которые оно будет визуализировать. Чтобы это продемонстрировать, мы создали новое строго типизированное частичное представление под названием MyStronglyTypedPartial.cshtml в папке /Views/Shared. Оно создается практически так же, как и обычное частичное представление, только нужно выбрать или ввести с клавиатуры тип для опции Model class, как показано на рисунке 18-10. Мы использовали тип IEnumerable<string>.

Рисунок 18-10: Создаем строго типизированное частичное представление

В листинге 18-25 показаны дополнения в файле частичного представления, который был создан Visual Studio. Изначально он содержит только тег модели @, который указывает тип модели представления.

Листинг 18-25: Создаем строго типизированное частичное представление
@model IEnumerable<string>

<div>
	This is the message from the partial view.
	<ul>
		@foreach (string str in Model)
		{
			<li>@str</li>
		}
	</ul>
</div>

Мы отображаем содержимое объекта модели представления как список HTML с помощью тега Razor @foreach. Чтобы продемонстрировать работу этого частичного представления, мы обновили файл /Views/Common/List.cshtml, как показано в листинге 18-26.

Листинг 18-26: Используем строго типизированное частичное представление
@{
	ViewBag.Title = "List";
	Layout = null;
}

<h3>This is the /Views/Common/List.cshtml View</h3>

@Html.Partial("MyStronglyTypedPartial", new[] { "Apple", "Orange", "Pear" })

Отличие от предыдущего примера заключается в том, что здесь мы передаем дополнительный аргумент во вспомогательный метод Partial, который определяет объект модели представления. Чтобы увидеть, как работает это строго типизированное частичное представление, запустите приложение и перейдите по ссылке /Home/List, как показано на рисунке 18-11.

Рисунок 18-11: Используем строго типизированное частичное представление

Используем дочерние действия

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

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

Создаем дочернее действие

Любое действие можно использовать как дочернее. Чтобы продемонстрировать работу дочерних действий, мы добавили новый метод действия в контроллер Home, как показано в листинге 18-27.

Листинг 18-27: Добавляем дочернее действие в контроллер Home
using System.Web.Mvc;
using System;

namespace WorkingWithRazor.Controllers
{
	public class HomeController : Controller
	{
		public ActionResult Index()
		{
			string[] names = {"Apple", "Orange", "Pear"};
			return View(names);
		}

		public ActionResult List()
		{
			return View();
		}

		[ChildActionOnly]
		public ActionResult Time()
		{
			return PartialView(DateTime.Now);
		}
	}
}

Наш метод действия называется Time и визуализирует частичное представление, вызывая метод PartialView (о котором мы рассказывали в главе 15). Атрибут ChildActionOnly гарантирует, что метод действия может быть вызван только как дочернее действие из представления. Хотя метод действия может использоваться как дочерний и без этого атрибута, мы всегда применяем ChildActionOnly, чтобы данный метод невозможно было вызвать на запрос пользователя.

После определения метода действия нам нужно создать частичное представление, которое он будет визуализировать. Дочерние действия обычно связываются с частичными представлениями, хотя это и необязательно. В листинге 18-28 показано представление /Views/Home/Time.cshtml, которое мы создали для работы с нашим дочерним действием. Это строго типизированное частичное представление, моделью которого является объект DateTime.

Листинг 18-28: Частичное представление для дочернего действия
@model DateTime

<p>The time is: @Model.ToShortTimeString()</p>

Визуализируем дочернее действие

Дочернее действие вызывается вспомогательным методом Html.Action. После данного вызова выполняется метод действия, обрабатывается ViewResult, и вывод внедряется в ответ клиенту. В листинге 18-29 показаны изменения, которые мы внесли в файл /Views/Common/List.cshtml, чтобы визуализировать дочернее действие.

Листинг 18-29: Вызов дочернего действия из представления List
@{
	ViewBag.Title = "List";
	Layout = null;
}

<h3>This is the /Views/Common/List.cshtml View</h3>

@Html.Partial("MyStronglyTypedPartial", new[] { "Apple", "Orange", "Pear" })

@Html.Action("Time")

Вы можете увидеть вывод дочернего действия, запустив приложение и снова перейдя по ссылке /Home/List, как показано на рисунке 18-12.

Рисунок 18-12: Используем дочернее действие

Вспомогательному методу Action, который был вызван в листинге 18-29, мы передали параметр с именем вызываемого метода действия. MVC Framework будет искать метод действия в контроллере, который обрабатывает текущий запрос. Чтобы вызвать метод действия из другого контроллера, передайте в параметр его имя, например:

@Html.Action("Time", "MyController")

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

[ChildActionOnly]
public ActionResult Time(DateTime time) 
{
	return PartialView(time);
}

можно вызвать из представления следующим образом:

@Html.Action("Time", new { time = DateTime.Now })