Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core
Хотя встроенные атрибуты валидации охватывают приличное количество ситуаций, которые могут возникнуть, но все же их бывает недостаточно. Но mvc фреймворк настолько гибкий, что позволяет создавать собственные атрибуты валидации и закладывать в них свою логику.
Все атрибуты валидации образованы от базового класса ValidationAttribute
, который находится в пространстве имен
System.ComponentModel.DataAnnotations
. Поэтому именно от этого класса мы будем образовывать свой атрибут.
Допустим, нам надо, чтобы какая-нибудь книга была написана ограниченным кругом авторов. Создадим соответствующий атрибут, который будет это проверять:
using System.ComponentModel.DataAnnotations; namespace Mvc4BasicApplication.Annotations { public class MyAuthorsAttribute : ValidationAttribute { //массив для хранения допустимых авторов private static string[] myAuthors; public MyAuthorsAttribute(string[] Authors) { myAuthors = Authors; } public override bool IsValid(object value) { if (value != null) { string strval = value.ToString(); for (int i = 0; i < myAuthors.Length; i++) { if (strval == myAuthors[i]) return true; } } return false; } } }
Чтобы применить логику валидации, надо переопределить метод IsValid
, предоставленный базовым классом.
Логика атрибута довольно проста - мы получаем массив допустимых авторов. А при получении значения валидируемого свойства проверяем, имеется ли оно в этом массиве авторов, в зависимости от чего возвращаем true или false.
Использование атрибута аналогично использованию других атрибутов валидации:
public class Book { [HiddenInput(DisplayValue = false)] public int Id { get; set; } [Required (ErrorMessage="Поле должно быть установлено")] [Display(Name = "Название")] public string Name { get; set; } [Required] [MyAuthors(new string[] { "Л. Толстой", "А. Пушкин", "Ф. Достоевский", "И. Тургенев" }, ErrorMessage="Недопустимый автор")] [Display(Name = "Автор")] public string Author { get; set; } [Required] [Display(Name = "Год")] [Range(1700,2000,ErrorMessage="Недопустимый год")] public int Year { get; set; } }
В итоге свойство будет невалидно, если был введен любой автор, не входящий в список.
Атрибуты валидации на уровне модели применяются к проверке комбинации свойств. Например, мы не хотим, чтобы пользователь мог ввести определенную книгу:
public class NotAllowedAttribute : ValidationAttribute { public override bool IsValid(object value) { Book b = value as Book; if (b.Name=="Преступление и наказание" && b.Author=="Ф. Достоевский" && b.Year==1866) { return false; } return true; } }
Тогда устанавливаем атрибут для всей модели:
[NotAllowedAttribute(ErrorMessage="Недопустимая книга")] public class Book { [HiddenInput(DisplayValue = false)] public int Id { get; set; } [Required (ErrorMessage="Поле должно быть установлено")] [Display(Name = "Название")] public string Name { get; set; } //....................... }
При попытке ввести указанные данные для книги мы получим ошибку:
Самовалидация представляет собой процесс, при котором модель запускает механизм валидации из себя самой. И сама инкапсулирует всю логику валидации.
Для этого класс модели должен реализовать интерфейс IValidatableObject:
public class Book : IValidatableObject { [HiddenInput(DisplayValue = false)] public int Id { get; set; } [Display(Name = "Название")] public string Name { get; set; } [Display(Name = "Автор")] public string Author { get; set; } [Display(Name = "Год")] public int? Year { get; set; } public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { List<ValidationResult> errors = new List<ValidationResult>(); if (string.IsNullOrWhiteSpace(this.Name)) { errors.Add(new ValidationResult("Введите название книги")); } if (string.IsNullOrWhiteSpace(this.Author)) { errors.Add(new ValidationResult("Введите автора книги")); } if (this.Year == null || this.Year<1700 || this.Year>2000) { errors.Add(new ValidationResult("Недопустимый год")); } return errors; } }
В данном случае нам надо реализовать метод Validate
и возвратить коллекцию объектов ValidationResult
,
которые и будут содержать все ошибки валидации.
В дополнение к выше рассмотренным методам переопределения механизма валидации мы также можем создать свой провайдер валидации.
Для этого мы должны создать класс производный от класса ModelValidatorProvider
и переопределить его метод GetValidators
.
public class MyValidationProvider : ModelValidatorProvider { public override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context) { if (metadata.ContainerType == typeof(Book)) { return new ModelValidator[] { new BookPropertyValidator(metadata,context)}; } if (metadata.ModelType == typeof(Book)) { return new ModelValidator[] { new BookValidator(metadata, context) }; } return Enumerable.Empty<ModelValidator>(); } }
Классы BookPropertyValidator
и BookValidator
мы расмотрим чуть позже, а пока посмотрим общую логику класса.
Метод GetValidators
вызывается для каждого свойства и отдельно для всей модели. Поэтому мы используем два валидатора -
BookPropertyValidator
(для свойств модели) и BookValidator
(для модели в целом).
С помощью переданного в качестве параметра объекта ModelMetadata
мы получаем некоторую информацию касательно объектов валидации. Так,
мы можем получить доступ к следующим свойствам данного объекта:
CotainerType. Это свойство возвращает тип валидируемой модели, которая содержит указанное свойство.
PropertyName. Это свойство возвращает имя валидируемого свойства
ModelType. Это свойство возвращает тип объекта модели
Теперь посмотрим на класс BookPropertyValidator
, который инкапсулирует логику валидации для отдельных свойств:
public class BookPropertyValidator : ModelValidator { public BookPropertyValidator(ModelMetadata metadata, ControllerContext context) : base(metadata, context) { } public override IEnumerable<ModelValidationResult> Validate(object container) { Book b = container as Book; if (b != null) { switch (Metadata.PropertyName) { case "Name" : if (string.IsNullOrEmpty(b.Name)) { return new ModelValidationResult[]{ new ModelValidationResult { MemberName="Name", Message="Введите название книги"} }; } break; case "Author": if (string.IsNullOrEmpty(b.Author)) { return new ModelValidationResult[]{ new ModelValidationResult { MemberName="Author", Message="Введите автора книги"} }; } break; case "Year": if (b.Year>2000 || b.Year<1700) { return new ModelValidationResult[]{ new ModelValidationResult { MemberName="Year", Message="Недопустимый год"} }; } break; } } return Enumerable.Empty<ModelValidationResult>(); } }
В методе Validate
мы определяем валидируемые свойства и вызываем соответствующие действия по валидации объекта.
Затем в объект ModelValidationResult
добавляем сведения касательно возникшей ошибки: свойство MemberName
указывает на имя
валидируемого свойства, а свойство Message
- на сообщение об ошибке валидации.
Похожим образом выглядит валидатор для всей модели - BookValidator
, только в объект ModelValidationResult
в качестве
значения свойства MemberName
мы передаем пустые кавычки:
public class BookValidator : ModelValidator { public BookValidator(ModelMetadata metadata, ControllerContext context) : base(metadata, context) { } public override IEnumerable<ModelValidationResult> Validate(object container) { Book b = (Book)Metadata.Model; List<ModelValidationResult> errors = new List<ModelValidationResult>(); if (b.Name == "Преступление и наказание" && b.Author == "Ф. Достоевский" && b.Year == 1866) { errors.Add(new ModelValidationResult { MemberName = "", Message = "Недопустимая книга" }); } return errors; } }
Последним шагом будет регистрация своего провайдера валидации в файле Global.asax:
public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { ModelValidatorProviders.Providers.Add(new MyValidationProvider()); Database.SetInitializer(new BookDbInitializer()); AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); } }
Теперь мы можем освободить нашу модель от всех атрибутов, так как вся логика валидации у нас уже заложена в провайдере:
public class Book { [HiddenInput(DisplayValue = false)] public int Id { get; set; } [Display(Name = "Название")] public string Name { get; set; } [Display(Name = "Автор")] public string Author { get; set; } [Display(Name = "Год")] public int Year { get; set; } }