Главная страница   /   2.4. Создание простого приложения по вводу данных (ASP.NET MVC 4

ASP.NET MVC 4

ASP.NET MVC 4

Адам Фриман

2.4. Создание простого приложения по вводу данных

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

Набросаем план

Давайте представим себе, что подруга решила провести вечеринку в канун Нового года, и что она попросила нас создать веб сайт, который позволит ее друзьям и знакомым принять приглашение с RSVP (подпись на приглашении, призывающая получателя дать ответ об участии в мероприятии). На сайте должно присутствовать следующее:

  • Главная страница, где отображается информация о вечеринке
  • Форма, которая может быть использована для RSVP
  • Валидация RSVP формы, которая отобразит страницу с благодарностью
  • Заполненный и отправленный ответ о согласии принять участие в вечеринке

В следующих разделах мы будем наращивать MVC проект, который мы создали в начале главы, и добавим эти возможности. Мы можем сделать первый пункт из списка, применив те знания то, которые мы получили ранее, то есть мы можем добавить HTML для наших существующих представлений, где будет дана подробная информация о вечеринке. В листинге 2-7 показаны дополнения, которые мы сделали в файле Views/Home/Index.cshtml.

Листинг 2-7: Отображение информации о вечеринке
@{
	Layout = null;
}

<!DOCTYPE html>
<html>
<head>
	<meta name="viewport" content="width=device-width" />
	<title>Index</title>
</head>
<body>
	<div>
		@ViewBag.Greeting World (from the view)
		<p>
			We're going to have an exciting party.<br />
			(To do: sell it better. Add pictures or something.)
		</p>
	</div>
</body>
</html>

Мы на правильном пути. Если вы запустите приложение, вы увидите информацию о вечеринке, ну, вернее, метку-заполнитель (placeholder) для этой информации, но вы можете уловить суть. Пример показан на рисунке 2-12.

Рисунок 2-12: Добавление представления

Проектирование модели данных

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

Нам не нужна сложная модель для приложения PartyInvites, но мы создадим один доменный класс, которые мы назовем GuestResponse. Этот объект будет отвечать за хранение, проверку и подтверждение RSVP.

Добавление класса модели

По MVC соглашению классы, которые составляют модель, помещаются в папку Models. Щелкните правой кнопкой мыши по Models в окне Solution Explorer и выберите Add, за которым следует Class, из всплывающего меню. Назовите файл GuestResponse.cs и нажмите кнопку Add, чтобы создать класс. Измените содержимое класса в соответствии с листингом 2-8.

Совет

Если у вас нет пункта меню Class, то вы, вероятно, оставили работать отладчик (дебаггер) Visual Studio. Visual Studio ограничивает изменения, которые можно внести в проект, если приложение запущено.

Листинг 2-8: Доменный класс GuestResponse
namespace PartyInvites.Models
{
	public class GuestResponse
	{
		public string Name { get; set; }
		public string Email { get; set; }
		public string Phone { get; set; }
		public bool? WillAttend { get; set; }
	}
}

Совет

Вы, возможно, заметили, что свойство WillAttend имеет тип bool? (Nullable<bool>), то есть оно может быть true, false или null. Мы объясним это в разделе «Добавление валидации» далее в этой главе.

Ссылка на метод действия

Одна из целей нашего приложение заключается во включении RSVP формы, поэтому нам нужно добавить ссылку на нее из нашего представления Index.cshtml, как показано в листинге 2-9.

Листинг 2-9: Добавление ссылки для RSVP формы
@{
	Layout = null;
}

<!DOCTYPE html>
<html>
<head>
	<meta name="viewport" content="width=device-width" />
	<title>Index</title>
</head>
<body>
	<div>
		@ViewBag.Greeting World (from the view)
		<p>
			We're going to have an exciting party.<br />
			(To do: sell it better. Add pictures or something.)
		</p>
		@Html.ActionLink("RSVP Now", "RsvpForm")
	</div>
</body>
</html>

Html.ActionLink является вспомогательным методом HTML. MVC Framework разработан с набором встроенных вспомогательных методов, которые удобны для обработки HTML ссылок, текстовых вводных данных, флажков, выборок и даже пользовательских элементов управления. Метод ActionLink принимает два параметра: первый – это текст для отображения в ссылке, а второй – это выполняемое действие, когда пользователь нажимает на ссылку. Мы объясним вспомогательные методы HTML в главах 19-21. На рисунке 2-13 показана ссылка, которую мы добавили.

Рисунок 2-13: Добавление в представление ссылки

Если вы наведете курсор мыши на ссылку в браузере, вы увидите, что ссылка указывает на http://yourserver/Home/RsvpForm. Метод Html.ActionLink проверил конфигурацию URL нашего приложения и определил, что /Home/RsvpForm является URL для действия RsvpForm контроллера HomeController. Обратите внимание, что в отличие от традиционных приложений ASP.NET, URL-адреса MVC не соответствуют физическим файлам. Каждый метод действия имеет свой URL, и MVC использует систему маршрутизации ASP.NET перевести эти URL в действия.

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

Если вы нажмете на ссылку, то увидите ошибку 404 Not Found. Это потому что пока еще мы не создали метод действия, который соответствует URL /Home/RsvpForm. Мы сделаем это путем добавления метода RsvpForm нашему классу HomeController, как показано в листинге 2-10.

Листинг 2-10: Добавление в контроллер нового метода действия
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace PartyInvites.Controllers
{
	public class HomeController : Controller
	{
		public ViewResult Index()
		{
			int hour = DateTime.Now.Hour;
			ViewBag.Greeting = hour < 12 ? "Good Morning" : "Good Afternoon";
			return View();
		}
		public ViewResult RsvpForm()
		{
			return View();
		}
	}
}

Добавление строго типизированного представления

Мы хотим добавить представление для нашего метода действия RsvpForm, но мы собираемся сделать кое-что больше: то есть создать строго типизированное представление. Строго типизированные представления предназначены для обработки определенного типа доменов. Если мы укажем тип, с которым мы хотим работать (в этом примере GuestResponse), MVC предоставит дополнительные возможности, чтобы упростить нам задачу.

Внимание

Убедитесь, что перед работой ваш MVC проект скомпилирован. Если вы создали класс GuestResponse, но не скомпилировали его, MVC не сможет создать строго типизированное представление для данного типа. Чтобы скомпилировать приложение, выберите Build Solution в Visual Studio меню Build.

Щелкните правой кнопкой мыши внутри метода действия RsvpForm и выберите для создания представления Add View из всплывающего меню. В диалоговом окне Add View поставьте галочку на Create a strongly-typed view и выберите опцию GuestResponse из выпадающего меню. Снимите флажок с Use a layout or master page и убедитесь, что Razor выбран в качестве движка представления, и что опция Scaffold template установлена на Empty, как показано на рисунке 2-14.

Рисунок 2-14: Добавление строго типизированного представления

Нажмите кнопку Add, и Visual Studio создаст новый файл с именем RvspForm.cshtml и откроет его для редактирования. Вы можете увидеть первоначальное содержание в листинге 2-12. Как вы заметили, это другой HTML файл, но он содержит Razor выражение @model. Вы сейчас увидите, что это является ключом к строго типизированному представлению и возможностям, которые оно предлагает.

Листинг 2-12: Начальное содержимое файла RsvpForm.cshtml
@model PartyInvites.Models.GuestResponse

@{
	Layout = null;
}

<!DOCTYPE html>
<html>
<head>
	<meta name="viewport" content="width=device-width" />
	<title>RsvpForm</title>
</head>
<body>
	<div>
	</div>
</body>
</html>

Построение формы

Теперь, когда мы создали строго типизированное представление, мы можем выстроить содержание RsvpForm.cshtml, чтобы превратить его в HTML форму для редактирования GuestResponse объектов. Измените представление так, чтобы оно соответствовало листингу 2-13.

Листинг 2-13: Создание представления формы
@model PartyInvites.Models.GuestResponse

@{
	Layout = null;
}

<!DOCTYPE html>
<html>
<head>
	<meta name="viewport" content="width=device-width" />
	<title>RsvpForm</title>
</head>
<body>
	@using (Html.BeginForm())
	{
		<p>Your name: @Html.TextBoxFor(x => x.Name) </p>
		<p>Your email: @Html.TextBoxFor(x => x.Email)</p>
		<p>Your phone: @Html.TextBoxFor(x => x.Phone)</p>
		<p>
			Will you attend?
			@Html.DropDownListFor(x => x.WillAttend, new[] {
					new SelectListItem() {Text = "Yes, I'll be there", Value = bool.TrueString},
					new SelectListItem() {Text = "No, I can't come", Value = bool.FalseString}
				}, "Choose an option")
		</p>
		<input type="submit" value="Submit RSVP" />
	}
</body>
</html>

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

@Html.TextBoxFor(x => x.Phone)

Вспомогательный метод HTML TextBoxFor генерирует HTML для элемента input, устанавливает параметр type на text и устанавливает атрибуты id и name на Phone, имя выбранного свойства доменного класса, вот так:

<input id="Phone" name="Phone" type="text" value="" />

Эта удобная функция работает, потому что наше представление RsvpForm строго типизировано, и мы сказали MVC, что GuestResponse это тот тип, который мы хотим обработать при помощи данного представления, поэтому вспомогательные методы HTML могут понять, какой тип данных мы хотим прочитать, с помощью выражения @model.

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

@Html.TextBox("Email")

Мы считаем, что методика лямбда-выражений помогает нам не делать опечаток в имени свойства типа модели, потому что всплывает Visual Studio IntelliSense и позволяет нам выбрать свойство автоматически, как показано на рисунке 2-15.

Рисунок 2-15: Visual Studio IntelliSense для лямбда-выражений во вспомогательных методах HTML

Другим удобным вспомогательным методом является Html.BeginForm, который генерирует элемент HTML формы, настроенный на обратную передачу данных методу действия. Поскольку мы не передали вспомогательному методу никаких параметров, он предполагает, что мы хотим передать обратно тот же URL. Ловким трюком является то, чтобы обернуть это в C# выражение using, вот таким образом:

@using (Html.BeginForm()) { 
    ...form contents go here... 
} 

Обычно, когда оно применяется таким образом, выражение using гарантирует, что объект удаляется, когда выходит из области видимости. Оно широко используется для подключения к базе данных, например, чтобы убедиться, что она закрывается, как только запрос был выполнен. (Это применение ключевого слова using отличается от того, что касается области видимости класса).

Вместо удаления объекта, помощник HtmlBeginForm закрывает HTML элемент form, когда он выходит из области видимости. Это означает, что вспомогательный метод Html.BeginForm создает обе части элемента form, например:

<form action="/Home/RsvpForm" method="post"> 
    ...form contents go here... 
</form> 

Не волнуйтесь, если вы не знакомы с удалением объектов в C#. Наша цель на данный момент состоит в том, чтобы показать, как создать форму с помощью вспомогательного метода HTML. Вы можете видеть форму в представлении RsvpForm, когда вы запустите приложение и нажмете ссылку RSVP Now. На рисунке 2-16 показан результат.

Рисунок 2-16: Представление RspvForm

Примечание

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

Обработка форм

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

Примечание

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

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

  • Метод, который отвечает на HTTP GET запросы: GET запрос является тем, с чем браузер имеет дело после каждого клика по ссылке. Этот вариант действий будет отвечать за отображение начальной пустой формы, когда кто-то первый раз посетит /Home/RsvpForm.
  • Метод, который отвечает на HTTP POST запросы: По умолчанию, формы, обрабатываемые при помощи Html.BeginForm(), отправляются браузером как POST запросы. Этот вариант действий будет отвечать за получение отправленных данные и решать, что с ними делать.

Обработка GET и POST запросов в отдельных методах C# помогает сохранить наш код опрятным, так как оба метода имеют различные обязанности. Оба метода действия вызываются одним и тем же URL, но MVC гарантирует, что будет вызван соответствующий метод в зависимости от того, имеем ли мы дело с GET или с POST запросом. В листинге 2-14 показаны изменения, которые мы должны сделать в классе HomeController.

Листинг 2-14: Добавление метода действия для поддержки POST запросов
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using PartyInvites.Models;

namespace PartyInvites.Controllers
{
	public class HomeController : Controller
	{
		public ViewResult Index()
		{
			int hour = DateTime.Now.Hour;
			ViewBag.Greeting = hour < 12 ? "Good Morning" : "Good Afternoon";
			return View();
		}

		[HttpGet]
		public ViewResult RsvpForm()
		{
			return View();
		}

		[HttpPost]
		public ViewResult RsvpForm(GuestResponse guestResponse)
		{
			// TODO: Email response to the party organizer
			return View("Thanks", guestResponse);
		}
	}
}

Мы добавили атрибут HttpGet для нашего существующего метода действия RsvpForm. Это говорит MVC, что данный метод должен использоваться только для GET запросов. Затем мы добавили перегруженную версию RsvpForm, который принимает параметр GuestResponse и применяет атрибут HttpPost. Атрибут говорит MVC, что новый метод будет иметь дело с POST запросами. Обратите внимание, что мы также импортировали пространство имен PartyInvites.Models – таким образом мы можем обратиться к типу модели GuestResponse без необходимости указывать имя класса. Мы расскажем, как работают наши дополнения в листинге, в следующих разделах.

Использование связывания данных модели

Первый вариант перегруженного метода действия RsvpForm обрабатывает то же представление, что и раньше. Она генерирует форму, показанную на рисунке 2-16. Второй вариант перегруженного метода является более интересным из-за параметра, но, учитывая, что метод действия будет вызываться в ответ на HTTP POST запрос, и что GuestResponse является типом класса C #, то как они объединены?

Ответом является связывание данных модели – чрезвычайно полезная функциональная особенность MVC, когда входные данные разбиваются (парсятся) и пары ключ/значение в HTTP запросе используются для заполнения свойств типа доменной модели. Этот процесс является противоположностью использования вспомогательных методов HTML; это когда при создании данных формы для отправки клиенту, мы генерировали HTML элементы input, где значения атрибутов id и name были получены из названий свойств классов моделей.

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

Модель представления данных является мощной и настраиваемой функцией, которая избавляет от рутины работы напрямую с HTTP-запросами и позволяет нам работать с C# объектами, а не иметь дело со значениями Request.Form[] и Request.QueryString[]. Объект GuestResponse, который передается в качестве параметра нашему методу действия, автоматически заполняется данными из полей формы. Мы рассмотрим подробно модель представления данных, а также в том числе, как ее можно настроить, в главе 22.

Обработка других представлений

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

return View("Thanks", guestResponse); 

Этот вызов метода View говорит MVC найти и обработать представление, которое называется Thanks, и передать представлению наш объект GuestResponse. Чтобы создать представление, которое мы указали, щелкните правой кнопкой мыши внутри одного из методов HomeController и выберите пункт Add View из всплывающего меню. Установите имя представления на Thanks, как показано на рисунке 2-17.

Рисунок 2-17: Добавление представления Thanks

Мы собираемся создать еще одно строго типизированное представление, поэтому поставьте галочку на этом пункте в диалоговом окне Add View. Класс данных, который мы выберем для этого представления, должен соответствовать классу, который мы передали представлению при помощи метода View. Поэтому убедитесь, что в выпадающем меню выбран GuestResponse. Убедитесь, что нет галочки на Use a layout or master page, View engine установлен на Razor, а также Scaffold template установлен на Empty.

Нажмите кнопку Add, чтобы создать новое представление. Поскольку представление связано с контроллером Home, MVC создаст представление как ~/Views/Home/Thanks.cshtml. Измените новое представление так, чтобы оно соответствовало листингу 2-15: мы выделили то, что нужно добавить.

Листинг 2-15: Представление Thanks
@model PartyInvites.Models.GuestResponse

@{
	Layout = null;
}

<!DOCTYPE html>
<html>
<head>
	<meta name="viewport" content="width=device-width" />
	<title>Thanks</title>
</head>
<body>
	<div>
		<h1>Thank you, @Model.Name!</h1>
		@if (Model.WillAttend == true)
		{
			@:It's great that you're coming. The drinks are already in the fridge!
		}
		else
		{
			@:Sorry to hear that you can't make it, but thanks for letting us know.
		}
	</div>
</body>
</html>

Представление Thanks использует Razor для отображения контента на основе значения свойства GuestResponse, которые мы передали методу View в методе действия RsvpForm. Razor оператор @model определяет тип доменной модели, с которым связано представление. Чтобы получить доступ к значению свойства доменного объекта, мы используем Model.PropertyName. Например, чтобы получить значение свойства Name, мы вызываем Model.Name. Не волнуйтесь, если вам не понятен синтаксис Razor, мы объясним это подробно в главе 5.

Теперь, когда мы создали представление Thanks, у нас есть базовый рабочий пример обработки формы при помощи MVC.

Запустите приложение в Visual Studio, нажмите на ссылку RSVP Now, добавьте в форму данные и нажмите на кнопку Submit RSVP. Вы увидите результат, показанный на рисунке 2-18 (хотя он может отличаться, если ваше имя не Джо, и вы сказали, что не сможете присутствовать).

Рисунок 2-18: Обработанное представление Thanks

Добавление валидации

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

В приложении MVC проверка, как правило, применяется к доменной модели, а не к пользовательскому интерфейсу. Это обозначает, что мы определяем наши критерии валидации в одном месте, и они вступают в силу в любом месте используемого класса модели. ASP.NET MVC поддерживает правила проверки, определяемые атрибутами пространства имен System.ComponentModel.DataAnnotations. В листинге 2-16 показано, как эти атрибуты могут быть применены к классу модели GuestResponse.

Листинг 2-16: Применение валидации к классу модели GuestResponse
using System.ComponentModel.DataAnnotations;

namespace PartyInvites.Models
{
	public class GuestResponse
	{
		[Required(ErrorMessage = "Please enter your name")]
		public string Name { get; set; }

		[Required(ErrorMessage = "Please enter your email address")]
		[RegularExpression(@".+\@.+\..+", ErrorMessage = "Please enter a valid email address")]
		public string Email { get; set; }

		[Required(ErrorMessage = "Please enter your phone number")]
		public string Phone { get; set; }

		[Required(ErrorMessage = "Please specify whether you'll attend")]
		public bool? WillAttend { get; set; }
	}
}

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

Совет

Как отмечалось ранее, мы использовали тип bool? для свойства WillAttend, поэтому мы смогли применить атрибут валидации Required. Если бы мы использовали обычный bool, значение, которое мы могли бы получить благодаря модели представления данных, могло бы быть только true или false, и мы не были бы сказать, выбрал ли пользователь значение. Тип bool? имеет три возможных значения: true, false и null. Значение null будет использовано, если пользователь не выбрал значение, и это заставляет атрибут Required сообщить об ошибке валидации.

Мы можем проверить, была ли ошибка валидации проверкой с помощью свойства ModelState.IsValid в нашем классе контроллера. В листинге 2-17 показано, как это можно сделать в нашем методе действия RsvpForm с поддержкой POST.

Листинг 2-17: Проверка на наличие ошибок при валидации формы
[HttpPost]
public ViewResult RsvpForm(GuestResponse guestResponse)
{
	if (ModelState.IsValid)
	{
		// TODO: Email response to the party organizer
		return View("Thanks", guestResponse);
	}
	else
	{
		// there is a validation error
		return View();
	}
}

Если ошибки валидации нет, мы говорим MVC обрабатывать представление Thanks, как мы делали ранее. Если ошибка валидации есть, мы вновь обрабатываем представление RsvpForm, вызвав метод View без параметров.

Просто отображать форму, когда есть ошибка, не очень полезно, мы должны дать пользователю некоторую информацию о том, в чем заключается проблема, и почему мы не можем принять его форму. Мы делаем это с помощью вспомогательного метода Html.ValidationSummary в представлении RsvpForm, как показано в листинге 2-18.

Листинг 2-18: Использование вспомогательного метода Html.ValidationSummary
@model PartyInvites.Models.GuestResponse

@{
	Layout = null;
}

<!DOCTYPE html>
<html>
<head>
	<meta name="viewport" content="width=device-width" />
	<title>RsvpForm</title>
</head>
<body>
	@using (Html.BeginForm())
	{
		@Html.ValidationSummary()
		<p>Your name: @Html.TextBoxFor(x => x.Name) </p>
		<p>Your email: @Html.TextBoxFor(x => x.Email)</p>
		<p>Your phone: @Html.TextBoxFor(x => x.Phone)</p>
		<p>
			Will you attend?
			@Html.DropDownListFor(x => x.WillAttend, new[] {
					new SelectListItem() {Text = "Yes, I'll be there", Value = bool.TrueString},
					new SelectListItem() {Text = "No, I can't come", Value = bool.FalseString}
				}, "Choose an option")
		</p>
		<input type="submit" value="Submit RSVP" />
	}
</body>
</html>

Если ошибок нет, метод Html.ValidationSummary создает скрытый элемент списка в качестве заполнителя в форме. MVC делает метку-заполнитель видимой и добавляет сообщения об ошибке, определяемые атрибутами валидации. Вы можете увидеть, как это выглядит, на рисунке 2-19.

Рисунок 2-19: Сводка результатов валидации

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

Примечание

Если вы работали с ASP.NET Web Forms, вы знаете, что в Web Forms существует понятие «серверные элементы управления», которые сохраняют состояние, сериализуя значения в скрытом поле формы __VIEWSTATE. Связывание данных ASP.NET MVC не привязано к Web Forms концепции серверных элементов управления, обратной передачи данных или View State. ASP.NET MVC не вводит скрытое поле __VIEWSTATE в обрабатываемые HTML страницы.

Выделение невалидных полей

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

Если свойство класса модели не прошло валидацию, вспомогательные методы HTML будут генерировать немного другой HTML. В качестве примера, вот HTML, который генерирует вызов Html.TextBoxFor (х => x.Name), когда нет ошибки валидации:

<input data-val="true" data-val-required="Please enter your name" id="Name" name="Name"
    type="text" value="" /> 

А вот HTML, который генерирует тот же вызов, когда пользователь не предоставил значение (что является ошибкой валидации, потому что мы применили атрибут Required свойства Name в классе модели GuestResponse):

<input class="input-validation-error" data-val="true" data-val-required="Please enter your 
name" id="Name" name="Name" type="text" value="" /> 

Мы выделили различия. Это вспомогательный метод добавил класс с именем input-validation-error. Мы можем воспользоваться этой функцией, создав таблицу стилей CSS, которая содержит стили для этого класса и других, что применяют различные вспомогательные методы HTML.

Соглашение в MVC проектах заключается в том, что статический контент, такой как таблицы стилей CSS, помещается в папку под названием Content. Мы создали папку Content, нажав правой кнопкой мыши по проекту PartyInvites в Solution Explorer и выбрав из всплывающего меню Add New Folder. Мы создали таблицу стилей, щелкнув правой кнопкой мыши по папке Content, выбрав Add New Item и затем выбрав Style Sheet в диалоговом окне Add New Item. Мы назвали нашу таблицу стилей Site.css, и это имя, которое Visual Studio использует при создании проекта с использованием иного шаблона MVC, а не Empty. Вы можете посмотреть содержимое файла Content/Site.css в листинге 2-19.

Листинг 2-19: Содержимое файла Content/Site.css
.field-validation-error {color: #f00;}
.field-validation-valid { display: none;}
.input-validation-error { border: 1px solid #f00; background-color: #fee; }
.validation-summary-errors { font-weight: bold; color: #f00;}
.validation-summary-valid { display: none;}

Чтобы использовать эту таблицу стилей, мы добавили новую ссылку в раздел head представления RsvpForm, как показано в листинге 2-20. Вы добавляете представлениям элементы link так же, как в обычном статическом HTML файле.

Листинг 2-20: Добавление элемента link в представление RsvpForm
@model PartyInvites.Models.GuestResponse

@{
	Layout = null;
}

<!DOCTYPE html>
<html>
<head>
	<meta name="viewport" content="width=device-width" />
	<link rel="stylesheet" type="text/css" href="~/Content/Site.css" />
	<title>RsvpForm</title>
</head>
<body>
	@using (Html.BeginForm())
	{
		@Html.ValidationSummary()
		<p>Your name: @Html.TextBoxFor(x => x.Name) </p>
		<p>Your email: @Html.TextBoxFor(x => x.Email)</p>
		<p>Your phone: @Html.TextBoxFor(x => x.Phone)</p>
		<p>
			Will you attend?
			@Html.DropDownListFor(x => x.WillAttend, new[] {
					new SelectListItem() {Text = "Yes, I'll be there", Value = bool.TrueString},
					new SelectListItem() {Text = "No, I can't come", Value = bool.FalseString}
				}, "Choose an option")
		</p>
		<input type="submit" value="Submit RSVP" />
	}
</body>
</html>

Совет

Если вы использовали MVC 3, то могли ожидать, чтобы мы добавим CSS файл к представлению, указав атрибут href как @Href("~/Content/Site.css") или @Url.Content("~/Content/Site.css"). С MVC 4 Razor автоматически обнаруживает атрибуты, которые начинаются с ~/, и автоматически вставляет для вас @Href или @Url.

Теперь будет отображаться визуально более очевидная ошибка валидации, если были представлены данные, которые вызвали эту ошибку, как показано на рисунке 2-20.

Рисунок 2-20: Автоматически выделенные ошибки валидации

Завершаем пример

Последнее требование к нашему примеру приложения заключается в том, чтобы отправить имейл с завершенными RSVP нашему другу, организатору вечеринки. Мы могли бы сделать это, добавив метод действия, чтобы создать и отправить по электронной почте сообщение, используя e-mail классы .NET Framework. Вместо этого мы собираемся использовать вспомогательный метод WebMail. Это не входит в рамки MVC, но это позволит нам завершить данный пример, не увязнув в деталях создания других средств отправки электронной почты.

Примечание

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

Мы хотим, чтобы имейл сообщение было отправлено, когда мы обрабатываем представление Thanks. В листинге 2-21 показаны изменения, которые мы должны сделать.

Листинг 2-21: Использование вспомогательного метода WebMail
@model PartyInvites.Models.GuestResponse

@{
	Layout = null;
}

<!DOCTYPE html>
<html>
<head>
	<meta name="viewport" content="width=device-width" />
	<title>Thanks</title>
</head>
<body>
	@{
		try
		{
			WebMail.SmtpServer = "smtp.example.com";
			WebMail.SmtpPort = 587;
			WebMail.EnableSsl = true;
			WebMail.UserName = "mySmtpUsername";
			WebMail.Password = "mySmtpPassword";
			WebMail.From = "rsvps@example.com";
			WebMail.Send("party-host@example.com", "RSVP Notification",
				Model.Name + " is " + ((Model.WillAttend ?? false) ? "" : "not")
					+ "attending");
		}
		catch (Exception)
		{
			@:<b>Sorry - we couldn't send the email to confirm your RSVP.</b>
		}
	}
	<div>
		<h1>Thank you, @Model.Name!</h1>
		@if (Model.WillAttend == true)
		{
			@:It's great that you're coming. The drinks are already in the fridge!
		}
		else
		{
			@:Sorry to hear that you can't make it, but thanks for letting us know.
		}
	</div>
</body>
</html>

Мы добавили Razor выражение, которое использует вспомогательный метод WebMail для настройки информации о нашем сервере электронной почты, включая имя сервера, требует ли сервер SSL соединения и данных учетной записи. После того как мы все настроили, мы используем метод WebMail.Send для отправки электронной почты.

Мы включили весь имейл код в блок try...catch, чтобы мы могли предупредить пользователя, если электронная почта не отправилась. Мы делаем это путем добавления текстового блока к выходным данным представления Thanks. Лучше было бы отобразить отдельное представление ошибки, если сообщение электронной почты не может быть отправлено, но мы хотели не усложнять наше первое MVC приложение.