Создание собственной логики валидации

Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core

Последнее обновление: 31.10.2015

Хотя встроенные атрибуты валидации охватывают приличное количество ситуаций, которые могут возникнуть, но все же их бывает недостаточно. Но 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

Самовалидация представляет собой процесс, при котором модель запускает механизм валидации из себя самой. И сама инкапсулирует всю логику валидации.

Для этого класс модели должен реализовать интерфейс 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; }
}
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850