Главная страница   /   6.1. Валидация на стороне сервера (ASP.NET MVC 4 в действии

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

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

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

6.1. Валидация на стороне сервера

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

Мы рассмотрим два ключевых понятия. Сначала разберем самый распространенный способ валидации на стороне сервера с ASP.NET MVC, используя Data Annotations. Потом мы исследуем метаданные модели и научимся писать собственные реализации провайдеров метаданных.

Валидация с Data Annotations

Библиотека Data Annotations, впервые реализованная в пакете .NET 3.5 SP1, представляет собой набор атрибутов и классов, определенных в сборке System.ComponentModel.DataAnnotations, которые позволяют добавить метаданные к классам. Метаданные описывают набор правил, с помощью которых можно определить, как проводить проверку конкретных объектов.

Кроме описания правил валидации, атрибуты DataAnnotations используются для реализации новых шаблонных функций, как вы видели в главе 3 на примере атрибутов DisplayName и DataType. Специальные атрибуты для контроля валидации приведены в таблице 6-1.

ASP.NET MVC включает в себя набор классов резервной валидации для всех атрибутов, которые отвечают за выполнение текущей проверки данных. Изучим атрибуты валидации на примере интерфейса, которому необходима валидация. На рисунке 6-1 показана форма ввода Edit с полями Company Name и Email Address.

Таблица 6-1: Атрибуты Data Annotations для валидации
Атрибут Описание
CompareAttribute Сравнивает значения двух свойств модели. Если они равны, валидация успешно завершена
RemoteAttribute Указывает JQuery Validate, библиотеке валидации на стороне клиента, что она должна вызвать действие для валидации на сервере, и выводит ее результат до отправки формы
RequiredAttribute Указывает, что требуется значение поля данных
RangeAttribute Устанавливает ограничения числового диапазона для значения поля данных
RegularExpressionAttribute Указывает, что значение поля данных должно соответствовать заданному регулярному выражению
StringLengthAttribute Задает максимальное число символов, которые разрешены в поле данных
Рисунок 6-1: Экран Edit с обязательным полем

В нашем приложении Company Name – обязательное для заполнения поле, Email Address - необязательное. Чтобы сделать поле Company Name обязательным, мы используем RequiredAttribute.

public class CompanyInput
{
	[Required]
	public string CompanyName { get; set; }
	[DataType(DataType.EmailAddress)]
	public string EmailAddress { get; set; }
}

Мы добавили RequiredAttribute к свойству CompanyName. Мы также добавили EmailAddress к атрибуту DataTypeAttribute, чтобы воспользоваться пользовательскими шаблонами для адресов электронной почты.

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

<h2>Edit</h2>
@using (Html.BeginForm("Edit", "Home")) {
	@Html.EditorForModel()
	<button type="submit">Submit</button>
}

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

Для более детального управления выводом, мы можем использовать методы расширения валидации HtmlHelper. Расширение ValidationSummary предоставляет сводный список ошибок валидации, который обычно отображается в верхней части формы. Чтобы выводить ошибки валидации для конкретных свойств модели, мы можем использовать метод ValidationMessage, а также ValidationMessageFor, основанный на выражениях.

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

[HttpPost]
public ActionResult Edit(CompanyInput input)
{
	if (ModelState.IsValid)
	{
		return View("Success");
	}
	return View(new CompanyInput());
}

В действии Edit метода POST мы сначала проверяем наличие ошибок в ModelState. Движок валидации MVC помещает ошибки валидации в ModelState, и их отсутствие отражается в свойстве IsValid. Если ошибок нет, мы выводим экран с сообщением об успешном заполнении формы. В противном случае мы отображаем исходный экран Edit, теперь с сообщением об ошибке валидации.

Чтобы продемонстрировать ошибку валидации в этом примере, просто отправим форму, не заполняя поле для названия компании. На этой странице поле Company name является обязательным. Результат показан на рисунке 6-2.

Рисунок 6-2: Ошибка валидации в результате отсутствия названия компании

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

Тем не менее, на рисунке 6-2 показана проблема, связанная с сообщением об ошибке валидации и самим экраном. И сообщение об ошибке, и название поля отображаются как "CompanyName" без пробела. Но мы хотим всегда включать пробелы между словами в названиях полей. Исправить название поля можно с помощью DisplayNameAttribute (часть пространства имен System.ComponentModel). Так как принято отображать имена свойств с пробелами между словами, мы расширим встроенный класс ModelMetadataProvider с помощью метода, который будет автоматически добавлять пробелы.

Расширение ModelMetadataProvider

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

Если мы хотим, чтобы метаданные для нашей модели извлекались из других источников, помимо Data Annotations, мы должны использовать ModelMetadataProvider.

Листинг 6-1: Абстрактный класс ModelMetadataProvider
public abstract class ModelMetadataProvider
{
	public abstract IEnumerable<ModelMetadata> GetMetadataForProperties
		(object container, Type containerType);

	public abstract ModelMetadata GetMetadataForProperty
		(Func<object> modelAccessor, Type containerType, string propertyName);

	public abstract ModelMetadata GetMetadataForType
		(Func<object> modelAccessor, Type modelType);
}

Класс ModelMetadataProvider содержит методы, которые получают ModelMetadata для каждого члена типа, для конкретно свойства и для частного типа, что показано в листинге 6.1.

Чтобы изменить отображаемый текст для конкретного свойства, нам нужно переопределить поведение базового класса DataAnnotationsModelMetadataProvider. Класс AssociatedMetadataProvider предоставляет общие функции для осуществления сценариев, в которых метаданные извлекаются из традиционных классов, свойств и атрибутов. Производным классам, таким как DataAnnotationsModelMetadataProvider, нужно всего лишь создать ModelMetadata уже уже существующих атрибутов.

В данном примере мы хотим изменить поведение DisplayName в модели метаданных. По умолчанию свойство DisplayName в ModelMetadata приходит от DisplayNameAttribute. Мы все еще хотим поддерживать значение DisplayName через атрибут.

В листинге 6-2 мы расширяем встроенный класс DataAnnotationsModelMetadataProvider создавая DisplayName из имени свойства, разделяя его пробелами.

Листинг 6-2: Пользовательский провайдер метаданных
public class ConventionProvider : DataAnnotationsModelMetadataProvider
{
	protected override ModelMetadata CreateMetadata(
			IEnumerable<Attribute> attributes,
			Type containerType,
			Func<object> modelAccessor,
			Type modelType,
			string propertyName)
	{
		var meta = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
		if (meta.DisplayName == null)
			meta.DisplayName = meta.PropertyName.ToSeparatedWords();
		return meta;
	}
}

Строка 3: Переопределяет CreateMetadata

Строка 10: Вызывает базовый метод

Строка 12: Разделяет слова в имени свойства пробелами

Чтобы создать соответствующую схему соглашения для отображения имен, мы создаем класс, который наследуется от класса DataAnnotationsModelMetadataProvider. Этот класс имеет довольно много встроенных возможностей, так что нам остается только переопределить метод CreateMetadata (строка 3). Базовый класс содержит поведение, которые мы хотим сохранить, поэтому мы сначала вызываем метод базового класса (строка 10) и сохраняем его результаты в локальной переменной. Так как мы могли поместить значение атрибута в DisplayName, теперь мы хотим изменить сценарий только в том случае, если значение DisplayName еще не было установлен. Итак, если оно не было установлено, мы хотим разделить имя свойства на отдельные слова с помощью расширенного метода ToSeparatedWords (строка 12). Наконец, мы возвращаем объект ModelMetadata, содержащий измененное имя.

Метод расширения ToSeparatedWords - довольно простое регулярное выражение для разделения идентификаторов на отдельные слова.

public static class StringExtensions
{
	public static string ToSeparatedWords(this string value)
	{
		if (value != null)
			return Regex.Replace(value, "([A-Z][a-z]?)", " $1").Trim();
		return value;
	}
}

Когда мы создали пользовательский ModelMetadataProvider, мы должны настроить ASP.NET MVC, чтобы его использовать. Такая настройка проводится в файле Global.asax:

protected void Application_Start()
{
	RegisterRoutes(RouteTable.Routes);
	ModelMetadataProviders.Current = new ConventionProvider();
}

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

Рисунок 6-3: Экран Edit с корректно отображающимися названиями полей и сообщением об ошибке.

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

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