Главная страница   /   4.2. Что должно входить в метод действия? (ASP.NET MVC 4 в действии

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

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

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

4.2. Что должно входить в метод действия?

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

Контроллер должен играть роль координатора – он, действительно, не должен содержать никакой бизнес-логики, а вместо этого выступать в роли одной из форм конвертирования, преобразующей вводимые пользователем данные (из представления) в объекты, которые могут использоваться доменной моделью (где находится бизнес-логика приложения), и наоборот. Этот процесс продемонстрирован на рисунке 4-1.

Рисунок 4-1: Контроллер является координатором между представлением и моделью

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

Преобразование моделей представлений вручную

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

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

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

Рисунок 4-2: Простая страница с информацией о комментариях

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

public class CommentSummary
{
	public string UserName { get; set; }
	public int NumberOfComments { get; set; }
}

Теперь нам необходимо создать действие контроллера (продемонстрировано в листинге 4.3), которое будет запрашивать у базы данных необходимые для отображения данные, а затем проецировать их в экземпляры класса CommentSummary.

Листинг 4-3: Проецирование данных гостевой книги в модель представления
public ActionResult CommentSummary()
{
	var entries = from entry in _db.Entries
		group entry by entry.Name
		into groupedByName
		orderby groupedByName.Count() descending
		select new CommentSummary
		{
			NumberOfComments =
				groupedByName.Count(),
			UserName = groupedByName.Key
		};
	return View(entries.ToList());
}

Строка 3: Извлекает данные гостевой книги

Строки 4-5: Группирует данные по именам пользователей

Строки 7-12: Проецирует их в модель представления

Строка 13: Отправляет модель представления в представление

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

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

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

Листинг 4-4: Отображение экземпляров класса CommentSummary в таблице
@model IEnumerable<Guestbook.Models.CommentSummary>
<table>
	<tr>
		<th>Number of comments</th>
		<th>User name</th>
	</tr>
	@foreach(var summaryRow in Model) {
		<tr>
			<td>@summaryRow.NumberOfComments</td>
			<td>@summaryRow.UserName</td>
		</tr>
	}
</table>

Автоматическое преобразование моделей представления

Кроме проецирования вручную в данном примере мы продемонстрировали, что для того чтобы преобразовывать доменные объекты в модели представления, записывая при этом меньший объем кода, вы можете воспользоваться таким инструментом, как библиотека с открытым исходным кодом AutoMapper. То, как эта библиотека может использоваться в MVC проектах, мы изучим в главе 11.

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

Помимо простых операций преобразования еще одной универсальной задачей контроллеров является выполнение валидации данных, вводимых пользователем.

Валидация вводимых данных

В главе 2 мы рассмотрели пример приема данных, вводимых пользователем, в действии Create нашего GuestbookController:

[HttpPost]
public ActionResult Create(GuestbookEntry entry)
{
	entry.DateAdded = DateTime.Now;
	_db.Entries.Add(entry);
	_db.SaveChanges();
	return RedirectToAction("Index");
}

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

Первое, что мы сделаем – это пометим свойства Name и Message класса GuestbookEntry атрибутами Required.

Листинг 4-5: Применение атрибутов валидации
public class GuestbookEntry
{
	public int Id { get; set; }
	[Required]
	public string Name { get; set; }
	[Required]
	public string Message { get; set; }
	public DateTime DateAdded { get; set; }
}

Строки 4, 6: Помечаем свойства атрибутами Required

Атрибут Required располагается в пространстве имен System.ComponentModel.DataAnnotations и обеспечивает возможность валидации конкретных свойств объекта (в этом пространстве имен присутствует несколько других атрибутов, к примеру, StringLengthAttribute, который выполняет проверку на непревышение максимальной длины строки, – мы рассмотрим эти атрибуты более детально в главе 6).

После выделения свойств Name и Message MVC будет автоматически проверять достоверность этих свойств при вызове действия Create. Мы можем проверить, выполняется ли валидация данных успешно или же не выполняется вовсе, путем проверки свойства ModelState.IsValid, а затем принятия решения о том, что делать в случае, если валидация не выполняется. Ниже представлена обновленная версия нашего действия Create.

Листинг 4-6: Проверка успешности выполнения валидации данных
[HttpPost]
public ActionResult Create(GuestbookEntry entry)
{
	if (ModelState.IsValid)
	{
		entry.DateAdded = DateTime.Now;
		_db.Entries.Add(entry);
		_db.SaveChanges();
		return RedirectToAction("Index");
	}
	return View(entry);
}

Строка 4: Проверяет, успешно ли выполнилась валидация

Строка 11: Заново отображает страницу в случае, если валидация не выполнилась

В этот раз вместо простого сохранения новой записи в базе данных мы сначала проверяем, возвращает ли свойство ModelState.IsValid значение true. Если это так, то мы продолжаем сохранять новую запись, как и делали это ранее. Тем не менее, если свойство возвращает значение false, то вместо этого мы заново отображаем представление Create, которое дает пользователю возможность исправить данные перед повторной отправкой формы.

Примечание

Помните, что вызов свойства ModelState.IsValid в действительности не выполняет валидацию данных; с помощью него всего лишь проверяется, выполнилась валидация успешно или нет. Сама валидация происходит перед вызовом действия контроллера.

Мы можем отобразить сообщение об ошибке, сгенерированное провалом выполнения валидации, путем вызова метода Html.ValidationSummary.

Листинг 4-7: Отображение в представлении сообщений об ошибках
@Html.ValidationSummary()
@using(Html.BeginForm()) {
	<p>Please enter your name: </p>
	@Html.TextBox("Name")
	<p>Please enter your message: </p>
	@Html.TextArea("Message", new{rows=10,cols=40})
	<br /><br />
	<input type="submit" value="Submit Entry" />
}

Строка 1: Выводит отчет, содержащий сообщения об ошибках

Строки 4, 6: Создает поля ввода посредством вспомогательных методов

Заметьте, что помимо вызова метода ValidationSummary в верхней части кода представления мы также использовали вспомогательные методы MVC для генерации текстовых полей на нашей странице (в главе 2 мы вручную записывали соответствующую разметку для элементов input и textarea). Одним из преимуществ использования этих вспомогательных методов является то, что MVC будет автоматически обнаруживать сообщения об ошибках валидации данных (потому как элементы имеют те же имена, что и свойства моделей недопустимого типа) и применять класс CSS, который может использоваться для указания на то, что в поле присутствует ошибка. В данном примере, поскольку в основе нашего приложения лежит предлагаемый по умолчанию шаблон MVC проекта, невалидные поля будут помечаться светло-красным задним фоном, как это показано на рисунке 4.3.

Рисунок 4-3: Отображение сообщений об ошибках и выделение невалидных полей

Сообщения об ошибках, которые вы видите на рисунке 4.3, являются используемыми по умолчанию в ASP.NET MVC сообщениями об обязательности заполнения полей. Мы можем переопределить эти сообщения и использовать вместо них собственные, изменив для этого объявление атрибута Required таким образом, чтобы в него входило пользовательское сообщение:

[Required(ErrorMessage = "Please enter your name")]

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

[Required(ErrorMessageResourceType = typeof(MyResources),
	ErrorMessageResourceName = "RequiredNameError")]

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