Большую роль в приложении играет валидация модели или проверка вводимых данных на корректность. Например, у нас есть класс пользователя, в котором определено свойство для хранения возраста. И нам было бы нежелательно, чтобы пользователь вводил какое-либо отрицательное число или заведомо невозможный возвраст, например, миллион лет.
Например, пусть у нас есть проект консольного приложения, в котором есть клас User:
public class User { public string Name { get; set; } public int Age { get; set; } public User(string name, int age) { Name = name; Age = age; } }
В программе мы можем проверять вводимые данные с помощью условных конструкций:
CreateUser("Tom", 37); CreateUser("b", -4); CreateUser("", 130); void CreateUser(string name, int age) { User user = new User(name, age); // проверяем корректность значения свойства Name // если его длина в диапазоне от 3 до 50, то оно корректно if (user.Name.Length >= 3 && user.Name.Length <= 50) Console.WriteLine($"Name: {user.Name}"); else Console.WriteLine("Incorrect name!"); // проверяем корректность значения свойства Age // если оно в диапазоне от 1 до 100, то оно корректно if (age >= 1 && age <= 100) Console.WriteLine($"Age: {user.Age}\n"); else Console.WriteLine("Incorrect age!\n"); } public class User { public string Name { get; set; } public int Age { get; set; } public User(string name, int age) { Name = name; Age = age; } }
Здесь предполагается, что имя должно иметь больше 1 символа, а возраст должен быть в диапазоне от 1 до 100. Однако в классе может быть гораздо больше свойств, для которых надо осуществлять проверки. А это привет к тому, что увеличится значительно код программы за счет проверок. К тому же задача валидации данных довольно часто встречается в приложениях. Поэтому фреймворк .NET предлагает гораздо более удобный функционал в виде атрибутов из пространства имен System.ComponentModel.DataAnnotations.
Итак, изменим касс User следующим образом:
using System.ComponentModel.DataAnnotations; public class User { [Required] [StringLength(50, MinimumLength = 3)] public string Name { get; set; } [Range(1, 100)] public int Age { get; set; } public User(string name, int age) { Name = name; Age = age; } }
Все правила валидации модели в System.ComponentModel.DataAnnotations определяются в виде атрибутов. В данном случае используются три атрибута:
классы RequiredAttribute, StringLengthAttribute и RangeAttribute. В коде необязательно использовать суффикс Attribute
, поэтому он,
как правило, отбрасывается. Атрибут Required требует обзательного наличия значения. Атрибут StringLength устанавливает максимальную и минимальную длину строки,
а атрибут Range устанавливает диапазон приемлемых значений.
Теперь изменим код программы:
using System.ComponentModel.DataAnnotations; CreateUser("Tom", 37); CreateUser("b", -4); CreateUser("", 130); void CreateUser(string name, int age) { User user = new User(name, age); var context = new ValidationContext(user); var results = new List<ValidationResult>(); if (!Validator.TryValidateObject(user, context, results, true)) { Console.WriteLine("Не удалось создать объект User"); foreach (var error in results) { Console.WriteLine(error.ErrorMessage); } Console.WriteLine(); } else Console.WriteLine($"Объект User успешно создан. Name: {user.Name}\n"); }
Здесь определен метод CreateUser, который принимает два значения и с их помощью создает объект User. В этом методе используются классы ValidationResult, Validator и ValidationContext, которые предоставляются пространством имен System.ComponentModel.DataAnnotations и которые управляют валидацией.
Вначале мы создаем контекст валидации - объект ValidationContext. В качестве первого параметра в конструктор этого класса передается валидируемый объект, то есть в данном случае объект User.
var context = new ValidationContext(user);
Собственно валидацию производит класс Validator и его метод TryValidateObject()
. В этот метод передается
валидируемый объект (в данном случае объект user), контекст валидации, список объектов ValidationResult и булевый параметр,
который указывает, надо ли валидировать все свойства.
var results = new List<ValidationResult>(); if (!Validator.TryValidateObject(user, context, results, true)) { //....... }
Если метод Validator.TryValidateObject()
возвращает false
, значит объект не проходит валидацию. Если модель не проходит валидацию,
то список объектов ValidationResult оказывается заполенным. А каждый объект ValidationResult содержит информацию о возникшей ошибке. Класс ValidationResult
имеет два ключевых свойства: MemberNames
- список свойств, для которых возникла ошибка, и ErrorMessage
- собственно сообщение об ошибке.
Для тестирования три раза вызываем метод CreateUser, передавая в него сначала корректные, а потом некорректные данные:
CreateUser("Tom", 37); CreateUser("b", -4); CreateUser("", 130);
И мы получим следующий консольный вывод:
Объект User успешно создан. Name: Tom Не удалось создать объект User The field Name must be a string with a minimum length of 3 and a maximum length of 50. The field Age must be between 1 and 100. Не удалось создать объект User The Name field is required. The field Age must be between 1 and 100.
В первом вызове метода CreateUser передаются корректные данные, поэтому никаких ошибок при валидации не возникнет.
А во втором вызове CreateUser валидация завершится неудачно, так как свойствам User переданы некорректные значения. Например, свойству Name передается
значение "b", что не соответствует правилам атрибута [StringLength(50, MinimumLength = 3)]
. Также значение свойства Age - -4 не соответствует
правилам атрибута [Range(1, 100)]
. Соответственно консоль отобразит ошибки для свойств Name и Age.
В третьем вызове CreateUser валидация также завершится неудачно - значение свойства Age - 130 также не соответствует
правилам атрибута [Range(1, 100)]
. Но, кроме того, свойству Name передается пустая строка - значение, которое не соответствует правилу атрибута
[Required]
. Данный атрибут требует обязательного наличия значения.
Если применяются типы record, то атрибуты валидации можно указать непосредственно перед определением свойства:
using System.ComponentModel.DataAnnotations; public record class User( [property: Required] [property: StringLength(50, MinimumLength = 3)] string Name, [property: Range(1, 100)] int Age );
В этом случае перед названием атрибута указывается оператор property:
Таким образом, вместо кучи условных конструкций для проверки значений свойств модели мы можем использовать один метод Validator.TryValidateObject()
,
а все правила валидации определить в виде атрибутов.