Несмотря на то, что .NET предоставляет нам большой набор встроенных атрибутов валидации, может потребоваться более изощренные в плане логики атрибуты. И в этом случае мы можем определить свои классы атрибутов.
Для создания атрибута нам надо унаследовать свой класс от класса ValidationAttribute и реализовать его метод IsValid()
:
При создании атрибута надо понимать, к чему именно он будет применяться - к свойству модели или ко всей модели в целом. Рассмотрим обе ситуации.
Определим следующий класс:
public class UserNameAttribute : ValidationAttribute { public override bool IsValid(object? value) { if (value is string userName) { if (userName != "admin") // если имя не равно admin return true; else ErrorMessage = "Некорректное имя: admin"; } return false; } }
Класс атрибута наследуется от класса ValidationAttribute и реалует его метод IsValid()
. В данный метод передается значение, которое валидируется.
Данный атрибут будет применяться к строковому свойству, поэтому в метод IsValid(object? value)
в качестве value будет передаваться строка.
Поэтому в ходе программы мы можем преобразовать значение value
к строке:
if (value is string userName)
Далее в методе мы проверяем, равно ли значение свойства строке "admin". Если НЕ равно, то возвращаем true. Это значит, что свойство, к которому будет применяться данный атрибут, прошло валидацию. Если же строка равна "admin", то возвращаем false и устанавливаем сообщение об ошибке. Если же в метод передана не строка, также возвращаем false.
Применим данный атрибут к свойству класса:
using System.ComponentModel.DataAnnotations; Validate(new User("Bob", 41)); Validate(new User("admin", 37)); void Validate(User user) { var results = new List<ValidationResult>(); var context = new ValidationContext(user); if (!Validator.TryValidateObject(user, context, results, true)) { foreach (var error in results) { Console.WriteLine(error.ErrorMessage); } } else Console.WriteLine("Пользователь прошел валидацию"); Console.WriteLine(); } public class User { [UserName] public string Name { get; set; } public int Age { get; set; } public User(string name, int age) { Name = name; Age = age; } }
В данном случае атрибут UserName применяется к свойству Name класса User:
[UserName] public string Name { get; set; }
Также, как и другие атрибуты, мы можем использовать эти атрибуты без суффикса Attribute.
В методе Validate валидируем объект User и выводим ошибки валидации на консоль. Поэтому при вызове Validate(new User("admin", 37))
объект User не пройдет валидацию, так как значени "admin" не соответствует правилам атрибута UserName. И в данном случае мы получим следующий консольный вывод:
Пользователь прошел валидацию Некорректное имя: admin
Подобным образом определим еще один атрибут, который будет применяться ко всему классу User в целом:
public class UserValidationAttribute : ValidationAttribute { public override bool IsValid(object? value) { if(value is User user) { if (user.Name == "Tom" && user.Age == 37) { ErrorMessage = "Имя не должно быть Tom и возраст одновременно не должен быть равен 37"; return false; } return true; } return false; } }
Поскольку атрибут будет применяться ко всей модели, то в метод IsValid в качестве параметра value
будет передаваться объект User. Как правило,
атрибуты, которые применяются ко всей модели, валидируют сразу комбинацию свойств класса. В данном случае смотрим, чтобы имя и возраст одновременно не были равны
"Tom" и 37.
Теперь применим этот атрибут:
using System.ComponentModel.DataAnnotations; Validate(new User("Bob", 41)); Validate(new User("Tom", 37)); void Validate(User user) { var results = new List<ValidationResult>(); var context = new ValidationContext(user); if (!Validator.TryValidateObject(user, context, results, true)) { foreach (var error in results) { Console.WriteLine(error.ErrorMessage); } } else Console.WriteLine("Пользователь прошел валидацию"); Console.WriteLine(); } [UserValidation] public class User { public string Name { get; set; } public int Age { get; set; } public User(string name, int age) { Name = name; Age = age; } }
Поскольку атрибут UserValidation должен применяться ко всему классу User, то он указывается непосредственно перед определением класса:
[UserValidation] public class User { }
В итоге если у объекта User одновременно имя будет "Tom", а возраст - 37, то такой объект User не пройдет валидацию. Консольный вывод:
Пользователь прошел валидацию Имя не должно быть Tom и возраст одновременно не должен быть равен 37
Выше оба атрибута сравнивали валидируемое значение с некоторым жестко заданным в коде набором значений. Однако мы можем определить через конструктор механизм передачи в атрибут значений из вне. Например, изменим атрибут UserValidation:
public class UserValidationAttribute : ValidationAttribute { public string InvalidName { get; set; } public int InvalidAge { get; set; } public UserValidationAttribute(string name, int age) { InvalidName = name; InvalidAge = age; } public override bool IsValid(object? value) { if(value is User user) { if (user.Name == InvalidName && user.Age == InvalidAge) { ErrorMessage = $"Имя не должно быть равно {InvalidName} и возраст одновременно не должен быть равен {InvalidAge}"; return false; } return true; } return false; } }
В данном случае значения, относительно которых будет проводиться валидация, передаются через конструктор и сохраняются в свойствах InvalidAge и InvalidName.
При применении атрибута теперь надо указать значения для параметров конструктора UserValidation:
[UserValidation("Bob", 41)] public class User { public string Name { get; set; } public int Age { get; set; } public User(string name, int age) { Name = name; Age = age; } }