Главная страница   /   3.2. Передача данных в представления (ASP.NET MVC 4 в действии

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

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

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

3.2. Передача данных в представления

На примере приложения "Guestbook" мы уже рассмотрели один из способов передачи в представление коллекции объектов GuestbookEntry (листинг 2-7). В этом разделе мы изучим еще три способа передачи данных в представление с помощью использования ViewDataDictionary и ViewBag, а также строго типизированных представлений.

Изучение ViewDataDictionary

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

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

public class GuestbookEntry
{
	public int Id { get; set; }
	public string Name { get; set; }
	public string Message { get; set; }
	public DateTime DateAdded { get; set; }
}

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

Листинг 3-1: Действие Show
public ViewResult Show(int id)
{
	var entry = _db.Entries.Find(id);
	bool hasPermission = User.Identity.Name == entry.Name;
	ViewData["hasPermission"] = hasPermission;
	return View(entry);
}

В базовом классе Controller мы имеем доступ к объекту ViewDataDictionary, который передается в представление при помощи свойства ViewData. Мы проверяем имя текущего пользователя, сравниваем его с записью гостевой книги, заданной в свойстве Name, и помещаем результат сравнения во ViewData с ключом hasPermission. Далее мы используем вспомогательный метод View для создания объекта ViewResult и устанавливаем в качестве значения свойства Model ViewData наш объект GuestbookEntry (то, как это делается, мы рассмотрим далее).

Мы вытащим информацию с ключом hasPermission из ViewData, и будем использовать ее для того, чтобы спрятать нашу ссылку Edit.

Листинг 3-2: Использование информации ViewData для скрытия ссылки
<p>
	@{
		bool hasPermission = (bool) ViewData["hasPermission"];
	}
	@if (hasPermission)
	{
		@Html.ActionLink("Edit", "Edit", new {id = Model.Id})
	}
	@Html.ActionLink("Back to Entries", "Index")
</p>

Строка 3: Доступ к ViewData

Строка 7: Отображает ссылку в зависимости от соответствия условию

Строка 9: Обратная гиперссылка на страницу "Index"

На представлении мы извлекаем информацию hasPermission из ViewData. Отображаем или не отображаем Edit в зависимости от значения переменной hasPermission. Наконец, мы выводим на экран гиперссылку для перенаправления пользователя обратно на страницу со списком записей гостевой книги. Страница, которая отображает запись гостевой книги, показана на рисунке 3-1.

Рисунок 3-1: Страница с подробной информацией о записи гостевой книги

Несмотря на то, что ViewDataDictionary довольно гибкий (внутри вы можете хранить любые данные), с точки зрения синтаксиса с ним не очень приятно работать – вам приходится выполнять приведение типов всякий раз, когда вам необходимо извлечь что-то из словоря. В ASP.NET MVC существует возможность использования альтернативного подхода для хранения динамических данных в ViewData – использование ViewBag.

ViewBag

Так же как и ViewDataDictionary, ViewBag позволяет передавать данные из контроллера в представление, но ViewBag использует возможности динамического языка программирования C# 4. Вместо хранения элементов в словаре посредством использования строкового ключа, вы можете просто задать для вашего контроллера значение динамического свойства ViewBag:

ViewBag.HasPermission = hasPermission;

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

<p>
	@if (ViewBag.HasPermission)
	{
		@Html.ActionLink("Edit", "Edit", new {id = Model.Id})
	}
	@Html.ActionLink("Back to Entries", "Index")
</p>

Несмотря на то, что динамический подход, заключающийся в использовании как ViewData, так и ViewBag, предоставляет значительную гибкость, он является довольно затратным. Код этих методик не оптимизирован должным образом, к тому же эти методики не позволяют компилятору исправлять ваши ошибки, если вы случайно перепутали тип динамического свойства. Кроме того, вы не сможете получить IntelliSense (встроенную подсказку) программы Visual Studio для динамических свойств или ViewData (хотя сторонние инструменты такие, как JetBrains ReSharper, поддерживают эту возможность).

Ко всему прочему, вы не сможете с легкостью привязать метаданные к динамическим свойствам. ASP.NET MVC для привязки метаданных к конкретным типам использует атрибуты (например, атрибуты валидации в пространстве имен System.ComponentModel.DataAnnotations могут использоваться для обозначения поля как обязательного для заполнения, или же для задания максимальной длины поля). Эти атрибуты не могут использоваться для динамических свойств ViewBag.

В качестве альтернативного варианта вы можете воспользоваться строго-типизированным представлением для указания на то, что представление может использоваться с конкретным известным строго типизированным классом. Преимуществом будет возможность воспользоваться IntelliSense и инструментами рефакторинга программы Visual Studio, а также появляется возможность использования метаданных, управляемых с помощью атрибутов. То, как это работает, мы рассмотрим в следующем разделе.

Строго типизированные представления и модели представления

При использовании представлений на базе движка Razor, представления могут по умолчанию наследоваться от двух типов: System.Web.Mvc.WebViewPage или System.Web.Mvc.WebViewPage<T>. Параметризованный WebViewPage<T> наследуется от WebViewPage, но предоставляет некоторые уникальные возможности, которые недоступны во WebViewPage.

Каркас определения членов класса WebViewPage<T> показано ниже.

Листинг 3-3: Каркас определения членов класса WebViewPage<T>
public class WebViewPage<TModel> : WebViewPage
{
	public new AjaxHelper<TModel> Ajax { get; set; }
	public new HtmlHelper<TModel> Html { get; set; }
	public new TModel Model { get; }
	public new ViewDataDictionary<TModel> ViewData { get; set; }
}

Строка 5: Строго типизированная модель представления

Помимо предоставления строго типизированной обертки для ViewData.Model посредством свойства Model, класс WebViewPage<T> предоставляет доступ к строго типизированным версиям вспомогательных методов представления – AjaxHelper и HtmlHelper.

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

Листинг 3-4: Передача коллекции записей гостевой книги в представление
public ActionResult Index()
{
	var mostRecentEntries = (from entry in _db.Entries
		orderby entry.DateAdded descending
		select entry).Take(20);
	var model = mostRecentEntries.ToList();
	return View(model);
}

В представлении Index, которое используется с этим методом действия, даже в слабо типизированном классе WebViewPage может использоваться свойство ViewData.Model. Но это свойство может быть только типа Object, и для эффективного использования полученного результата нам пришлось бы выполнить преобразование типов. Вместо этого мы можем задать тип модели для нашего базового класса WebViewPage<T> при помощи ключевого слова @model:

@using Guestbook.Models
@model List<GuestbookEntry>

В результате определения типа модели посредством ключевого слова @model наше представление теперь наследуется от класса WebViewPage<T>, а не от WebViewPage. Мы также использовали ключевое слово @using для импорта пространств имен. В следующем разделе мы рассмотрим, как можно использовать объект модели представления для того, чтобы отобразить ту информацию, которая содержится в представлении.

Отображение данных модели в представлении

Обычно для отображения информации, содержащейся в представлении, можно использовать объект HtmlHelper, который способствует получению модели представления для дальнейшей генерации HTML-страниц. Изучите приведенный ниже листинг, в котором мы отображаем окончательный вариант записи гостевой книги.

Листинг 3-5: Отображение записи гостевой книги в нашем представлении
<h2>Guestbook Entry</h2>
<dl>
	<dt>Name:</dt>
	<dd>@Model.Name</dd>
	<dt>Date Added:</dt>
	<dd>@Model.DateAdded</dd>
	<dt>Message:</dt>
	<dd>@Model.Message</dd>
</dl>
<p>
	@{
		bool hasPermission =
			(bool) ViewData["hasPermission"];
	}
	@if (hasPermission)
	{
		@Html.ActionLink("Edit", "Edit", new {id = Model.Id})
	}
	@Html.ActionLink("Back to Entries", "Index")
</p>

Строки 4, 6, 8: Выводит информацию о записи гостевой книги

Строка 11: Оператор многострочного кода движка Razor

Строка 15: Оператор if движка Razor

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

В "Guestbook Entry" мы отображаем подробную информацию о записи гостевой книги, переданную в нашу модель представления. Далее мы используем оператор многострочного кода движка Razor для извлечения из ViewData значения с ключом hasPermission. С операторов многострочного кода движка Razor начинается блок кода, отмеченный символом @, за которым следует открытая фигурная скобка: @{. Наконец, мы используем оператор if движка Razor для того, чтобы принять решение о том, нужно ли выводить Edit. Поскольку мы не хотим столкнуться с миллиардом скриптовых атак, которые возможны при отображении незакодированных пользовательских данных, то данные автоматически кодируются по умолчанию прежде, чем будут отображены на экране. Для того чтобы отобразить незакодированную информацию на экране мы можем воспользоваться методом Html.Raw, используемый для принудительного отображения необработанного текста.

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

Листинг 3-6: Класс LogOnModel
public class LogOnModel
{
	[Required]
	[Display(Name = "User name")]
	public string UserName { get; set; }
	[Required]
	[DataType(DataType.Password)]
	[Display(Name = "Password")]
	public string Password { get; set; }
	[Display(Name = "Remember me?")]
	public bool RememberMe { get; set; }
}

Строки 3-4, 6-8, 10: Применение атрибутов DataAnnotations

Класс LogOnModel довольно прост и содержит только автоматические свойства. Атрибуты, которые вы видите в листинге выше, – это DataAnnotations, более подробно вы изучите их в главе 4. На странице "Log On" (Авторизация) демонстрируются входные элементы для каждого из этих свойств, как вы можете видеть это на рисунке 3-2.

Рисунок 3-2: Страница Log On

В связи с тем, что для страницы "Log On" мы выбрали строго типизированное представление, при отображении HTML для каждого входного элемента мы можем применять встроенные вспомогательные методы. Вместо использования слабо связанных строк для представления параметров методов действий, для создания различных типов входных элементов мы можем воспользоваться преимуществами расширений HtmlHelper, в основе которых лежат выражения, как это показано ниже.

Листинг 3-7: Отображение формы ввода информации об аккаунте
@using (Html.BeginForm()) {
@Html.ValidationSummary(true, "Account creation was unsuccessful.  Please correct the errors and try again.")
<div>
	<fieldset>
		<legend>Account Information</legend>
		<div class="editor-label">
			@Html.LabelFor(m => m.UserName)
		</div>
		<div class="editor-field">
			@Html.TextBoxFor(m => m.UserName)
			@Html.ValidationMessageFor(m => m.UserName)
		</div>
		<div class="editor-label">
			@Html.LabelFor(m => m.Email)
		</div>
		<div class="editor-field">
			@Html.TextBoxFor(m => m.Email)
			@Html.ValidationMessageFor(m => m.Email)
		</div>
		<div class="editor-label">
			@Html.LabelFor(m => m.Password)
		</div>
		<div class="editor-field">
			@Html.PasswordFor(m => m.Password)
			@Html.ValidationMessageFor(m => m.Password)
		</div>
		<div class="editor-label">
			@Html.LabelFor(m => m.ConfirmPassword)
		</div>
		<div class="editor-field">
			@Html.PasswordFor(m => m.ConfirmPassword)
			@Html.ValidationMessageFor(m => m.ConfirmPassword)
		</div>
		<p>
			<input type="submit" value="Register" />
		</p>
	</fieldset>
</div>
}

Строка 7: Строго типизированный вспомогательный метод для метки

Строка 10: Строго типизированный вспомогательный метод для текстового поля

Строка 11: Строго типизированный вспомогательный метод для сообщения о валидации

В приведенном выше листинге мы воспользовались преимуществами нескольких методов расширений HtmlHelper, созданных для строго типизированных страниц представлений, включая методы для отображения надписей, полей ввода текста и сообщений подтверждения корректности введенных данных. Вместо того чтобы использовать для представления свойств слабо типизированной строки, подобную тем, что применялись в ASP.NET MVC 1 (@Html.TextBox("UserName")), эти вспомогательные методы используют возможность языка C# 3.5 для генерации HTML-разметки. Поскольку эти HTML-элементы должны быть сгенерированы так, чтобы соответствовать свойствам объектов, необходимо всего лишь задать условие, что для генерации соответствующей HTML-разметки первоначальные типы и объекты используются с выражениями.

Методы Html.LabelFor и Html.TextBoxFor, используемые для свойства UserName, как это показано в листинге 3-7, генерируют приведенную ниже HTML-разметку.

Листинг 3-8: HTML-разметка, сгенерированная с помощью вспомогательных методов HtmlHelper, базирующихся на выражениях
<label for="UserName">User name</label>
<input id="UserName" name="UserName" type="text" value="" />

Для того чтобы на нашей странице выполнялась проверка возможности доступа, в каждый элемент ввода (например, вторая строка листинга 3-8) должен входить соответствующий элемент label (к примеру, первая строка листинга). Так как наши элементы label и input сгенерированы посредством использования выражений, то нам больше не нужно беспокоиться о жестко закодированных названиях этих элементов.

Расширения HtmlHelper, созданные для строго типизированных представлений (включая те, что использовались в предыдущем листинге), перечислены в таблице 3-1.

Таблица 3-1: Вспомогательные методы в ASP.NET MVC
Вспомогательный метод Описание
DisplayFor Возвращает HTML-разметку для каждого свойства объекта, представленного с помощью выражения
DisplayTextFor Возвращает HTML-разметку для каждого свойства объекта, представленного с помощью конкретного выражения
EditorFor Возвращает HTML-элемент ввода данных для каждого свойства объекта, представленного с помощью конкретного выражения
CheckBoxFor Возвращает элемент ввода данных типа CheckBox для каждого свойства объекта, представленного с помощью конкретного выражения
DropDownListFor Возвращает HTML-элемент типа DropdownList для каждого свойства объекта, представленного с помощью конкретного выражения, в котором используется определенный список элементов
HiddenFor Возвращает скрытый HTML-элемент ввода данных для каждого свойства объекта, представленного с помощью конкретного выражения
LabelFor Возвращает HTML-элемент типа Label и собственное имя свойства, представленного с помощью конкретного выражения
ListBoxFor Возвращает HTML-элемент типа ListBox для каждого свойства объекта, представленного с помощью конкретного выражения, который использует предоставленные данные для формирования списка элементов
PasswordFor Возвращает элемент ввода пароля для каждого свойства объекта, представленного с помощью конкретного выражения
RadioButtonFor Возвращает элемент ввода данных типа RadioButton для каждого свойства объекта, представленного с помощью конкретного выражения
TextAreaFor Возвращает HTML-элемент типа TextArea для каждого свойства объекта, представленного с помощью конкретного выражения, использующего предоставленные данные для списка элементов
TextBoxFor Возвращает элемент типа TextBox для каждого свойства объекта, представленного с помощью конкретного выражения
ValidateFor Извлекает метаданные и проверяет корректность данных каждого поля, представленного с помощью конкретного выражения
ValidationMessageFor Возвращает HTML-разметку сообщения об ошибке валидации данных каждого поля, представленного с помощью выражения

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

Листинг 3-9: Регистрация действия LogOn посредством использования модели представления в качестве параметра этого действия
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
	// Тело метода действия
	...
}

Как видите, наш метод действия LogOn принимает в качестве параметра объект LogOnModel, а так же возможный возвращаемый URL-адрес вместо того, чтобы использовать каждый элемент ввода нашей формы в качестве отдельного параметра.

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