Создание своих атрибутов валидации

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

Несмотря на то, что .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;
    }
}
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850