Главная страница   /   17.1. Устранение возможности дублирования представлений (ASP.NET MVC 4 в действии

ASP.NET MVC 4 в действии

ASP.NET MVC 4 в действии

Джеффри Палермо

17.1. Устранение возможности дублирования представлений

В ASP.NET MVC возможность использования элементов управления для инкапсуляции сложных элементов пользовательского интерфейса почти исчезла. Мы можем использовать веб-элементы управления, которые не пользуются преимуществами ViewState, но которые отображают веб-элементы управления, сконструированные на Web Forms и являющиеся, в основном, бесполезными. Но вместо этого нам приходится обращаться к другим способам устранения дублирования в наших представлениях.

В ASP.NET MVC входят следующие средства перехвата дублирования в представлениях:

  • Шаблоны
  • Макеты
  • Частичные представления
  • Дочерние действия

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

В нашем первом примере мы рассмотрим создание сквозных шаблонов при помощи мастер-страниц.

Макеты

При использовании движка представления Razor мы добавляем возможность применения макетов как одной из составляющих наших представлений. Аналогично мастер-страницам, добавленным в качестве составляющей ASP.NET 2.0, макеты дают возможность разработчикам создавать мастер-макеты для универсальных страниц. Макет определяет универсальный шаблон, позволяя элементам-заполнителям (placeholders) для производных страниц или других макетов формироваться в свободных местах.

В приведенном ниже листинге макет определяет элементы-заполнители как для заголовка страницы, так и для основного контента.

Листинг 17-1: Мастер-страница, определенная для MVC представления
<!DOCTYPE html>
<html>
<head>
	<title>@ViewBag.Title</title>
	<link href="@Url.Content("~/Content/Site.css")"
		rel="stylesheet" type="text/css" />
	<script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")"
		type="text/javascript"></script>
</head>
<body>
	<div class="page">
		<div id="header">
			<div id="title">
				<h1>My MVC Application</h1>
			</div>
			<div id="logindisplay">
				@Html.Action("LogOnWidget", "Account")
			</div>
			<div id="menucontainer">
				<ul id="menu">
					<li>@Html.ActionLink("Home", "Index", "Home")</li>
					<li>@Html.ActionLink("Profiles", "Index", "Profile")</li>
					<li>@Html.ActionLink("About", "About", "Home")</li>
				</ul>
			</div>
		</div>
		<div id="main">
			@RenderBody()
			<div id="footer">
			</div>
		</div>
	</div>
</body>
</html>

Строки 21-23: Генерируют ссылки меню

Макеты в ASP.NET MVC похожи на мастер-страницы в Web Forms. Мы можем определять элементы-заполнители для контента, размещать универсальную разметку, а также применять сквозной макет.

В ASP.NET MVC макет Razor, в отличие от мастер-страниц, не имеет структуры отдельного класса. Макет имеет доступ к тем же свойствам, что и представление Razor, а именно:

  • AjaxHelper (через свойство Ajax).
  • HtmlHelper (через свойство Html).
  • ViewData и модель.
  • UrlHelper (через свойство Url).
  • TempData и ViewContext.

В листинге 17-1 мы использовали объект HtmlHelper для генерации универсальных ссылок меню. В нашем макете мы можем задать универсальный тип модели, но поскольку макет используется с множеством представлений, нет причины определять для всего приложения один единственный тип модели представления.

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

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

Для определения макета внутри представления мы можем воспользоваться свойством Layout:

@{
	Layout = "~/Views/Shared/_Layout.cshtml";
}

Или же мы можем определить макет глобально, внутри специального файла _ViewStart.cshtml. Этот файл, продемонстрированный на рисунке 17-1, содержит какой-то код, записанный с помощью синтаксиса Razor, который нам хотелось бы выполнить в начале парсинга представления Razor. Чаще всего этот код будет устанавливать свойство Layout, используемое для всех представлений.

Рисунок 17-1: Файл _ViewStart, содержащий код для задания макета по умолчанию

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

Частичные представления

Когда дело доходит до отображения общих фрагментов контента, перед нами предстает множество вариантов объединения этих фрагментов в общую логику отображения. После добавления в ASP.NET MVC 2 шаблонов в большинстве ситуаций, в которых мы могли бы использовать частичные представления, теперь используются шаблоны. Но мы все еще можем столкнуться с ситуациями, в которых нам предпочтительнее не применять инфраструктуру шаблонизации, а вместо этого точно указать, какую часть представления необходимо отобразить.

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

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

Механизм отображения частичных представлений довольно прост. Мы можем использовать метод RenderPartial или метод Partial родительского представления, как это показано ниже:

Листинг 17-2: Отображение частичного представления из родительского представления
@model IEnumerable<Profile>
<h2>Profiles</h2>
<table>
	<tr>
		<th>Username</th>
		<th>First name</th>
		<th>Last name</th>
		<th>Email</th>
	</tr>
	@foreach (var profile in Model) {
		@Html.Partial("_Profile", profile)
	}
</table>

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

Метод RenderPartial принимает имя частичного представления и необязательный параметр – модель. Имя частичного представления используется для определения места размещения разметки путем просмотра конкретных, хорошо известных путей поиска в следующем порядке:

  1. <Area>\<Controller>\<PartialName>.cshtml.
  2. <Area>\Shared\<PartialName>.cshtml.
  3. \<Controller>\<PartialName>.cshtml.
  4. \Shared\<PartialName>.cshtml.

Эти пути поиска схожи с теми, которые используются при поиске представлений по имени, с тем лишь исключением, что теперь мы ищем частичное представление по имени, указанному в методе RenderPartial. Для предотвращения случайного использования частичного представления из действия мы прибавим к имени представления спереди символ подчеркивания. Также мы могли бы использовать Html.RenderPartial("Profile", profile). Различие заключается в том, что Html.RenderPartial(...) – это void-метод, который отображает частичное представление непосредственно в поток отклика, между тем как Html.Partial(...) возвращает строку и отображается непосредственно в представление. В Razor Html.RenderPartial должен находиться в блоке кода.

В нашем примере в листинге 17-2 вызов Partial выполняет поиск файла с названием "Profile", находящимся в папке View конкретного контроллера, как это показано на рисунке 17-2.

Рисунок 17-2: Частичное представление Profile, расположенное в нашей папке Views\Profile

Частичное представление Profile – это файл с расширением .cshtml. По умолчанию частичные представления не принимают импортируемые по умолчанию настройки _ViewStart, что означает, что не используется ни один макет. Тем не менее, мы все еще можем определить макет в нашем частичном представлении, если нам это потребуется.

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

Листинг 17-3: Частичное представление для отображения строки модели Profile
@model AccountProfile.Models.Profile
<tr>
	<td>@Model.FirstName</td>
	<td>@Model.LastName</td>
	<td>@Model.Email</td>
</tr>

Что касается строго типизированных частичных представлений, то свойство Model теперь отражает объект Profile.

Частичные представления хорошо применять при отображении общих фрагментов контента из действия контроллера уже в основной модели. Кроме других виджетов, нам необходимо рассмотреть такую возможность ASP.NET MVC, как дочерние действия.

Дочерние действия

Частичные представления хорошо работают при отображении информации уже в основной модели представления, но они не применимы, когда отображаемую модель необходимо получить из другого источника. Например, виджет авторизации может отображать имя текущего пользователя и e-mail, но остальная часть страницы, вероятно, отображает информацию, не имеющую никакого отношения к текущему пользователю. Мы могли бы передать эту не относящуюся к текущему пользователю модель посредством ViewDataDictionary, но на данном этапе мы возвращаемся к "волшебным" строкам в нашем действии, наряду с проблемами трассировки модели обратно в ее источник.

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

Листинг 17-4: Отображение дочернего действия для виджета авторизации
<div id="logindisplay">
	@Html.Action("LogOnWidget", "Account")
</div>

На нашей мастер-странице мы хотим отображать универсальный виджет авторизации. Если пользователь не вошел в систему, этот виджет должен отобразить ссылку Login. В противном случае он может отображать общую информацию о текущем пользователе, например, имя пользователя и e-mail, а также ссылку на профиль пользователя. Но для того чтобы предоставить эту дополнительную информацию, нам не нужно помещать нагрузку в каждое действие, которое тем или иным образом отображает эту мастер-страницу. Возможно, информацию о профиле необходимо будет вытаскивать из постоянного хранилища такого, как база данных или сессия, поэтому мы и не хотим использовать для этого частичное представление.

В листинге 17-4 мы используем метод Action для отображения действия LogOnWidget AccountController. Метод Action схож с другими, базирующимися на действиях расширениями HtmlHelper, например, ActionLink, но Action будет отображать результаты этого строкового действия. Поскольку Action будет создавать еще один запрос к ASP.NET MVC, мы можем инкапсулировать сложные виджеты в обычный MVC паттерн.

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

Листинг 17-5: Наше дочернее действие виджета авторизации
[ChildActionOnly]
public PartialViewResult LogOnWidget()
{
	bool isAuthenticated = Request.IsAuthenticated;
	Profile profile = null;
	if (isAuthenticated)
	{
		var username = HttpContext.User.Identity.Name;
		profile = _profileRepository.Find(username);
		if (profile == null)
		{
			profile = new Profile(username);
			_profileRepository.Add(profile);
		}
	}
	var model = new LogOnWidgetModel(isAuthenticated, profile);
	return PartialView(model);
}

Строка 1: Вызывается только через RenderAction

Строка 4: Проверка на то, что пользователь аутентифицирован

Строка 9: Поиск пользовательского профиля

Строка 17: Отображение частичного представления

Несмотря на то, что логика отображения виджета авторизации сложна, мы можем инкапсулировать эту сложность в обычном действии контроллера. В нашем дочернем действии мы проверяем, вошел ли пользователь в систему. Если это так, то мы подтягиваем его профиль посредством IProfileRepository. Наконец, мы отображаем строго типизированное представление путем построения LogOnWidgetModel и вызова вспомогательного метода PartialView. Частичные представления в своих отображениях не содержат настройки по умолчанию _ViewStart. Для того чтобы убедиться в том, что это действие может отображаться только как дочернее действие, а не посредством внешнего запроса, мы помечаем наше дочернее действие атрибутом ChildActionOnly.

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

Мы рассмотрели большинство форм дублирования, с которыми можем столкнуться при построении представлений, но при построении списка параметров строки запроса для методов действий появляется еще одно семейство дублирования, которое может привести к неявным ошибкам. В следующем разделе мы рассмотрим то, как можно эффективно формировать список параметров без обращения к безымянным объектам или неприглядному синтаксису словаря.