Встроенные атрибуты валидации охватывают большую часть возможных ситуаций, тем не менее этих атрибутов может оказаться недостаточно, особенно в каких-то специфических сценариях. Однако .NET позволяет определять собственные атрибуты валидации со своей логикой.
Для создания атрибута валидации необходимо унаследовать класс атрибута от ValidationAttribute и переопределить его метод IsValid().
Допустим, мы хотим, чтобы вводимое имя пользователя было ограничено некоторым диапазоном имен:
using System.ComponentModel.DataAnnotations; namespace MvcApp { public class PersonNameAttribute : ValidationAttribute { //массив для хранения допустимых имен string[] _names; public PersonNameAttribute(string[] names) { _names = names; } public override bool IsValid(object? value) { return value != null && _names.Contains(value); } } }
Чтобы применить логику валидации, надо переопределить метод IsValid, предоставленный базовым классом. Параметр этого метода как раз и представляет то значение, которое надо валидировать.
Логика атрибута довольно проста - мы получаем массив допустимых имен. А при получении значения валидируемого свойства проверяем, имеется ли оно в этом массиве авторов, в зависимости результата возвращаем true или false.
Использование атрибута аналогично использованию других атрибутов валидации:
public class Person { [PersonName(new string[] {"Tom", "Sam", "Alice" }, ErrorMessage ="Недопустимое имя")] public string? Name { get; set; } public string? Email { get; set; } public string? Password { get; set; } public int Age { get; set; } }
Допустим, представление для ввода данных выглядит следующим образом:
@model MvcApp.Models.Person @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers <form method="post"> <div asp-validation-summary="All"></div> <p> <label asp-for="Name">Name</label><br /> <input type="text" asp-for="Name" /> <span asp-validation-for="Name" /> </p> <p> <label asp-for="Email">Email</label><br /> <input type="text" asp-for="Email" /> <span asp-validation-for="Email" /> </p> <p> <label asp-for="Password">Password</label><br /> <input type="password" asp-for="Password" /> <span asp-validation-for="Password" /> </p> <p> <label asp-for="Age">Age</label><br /> <input asp-for="Age" /> <span asp-validation-for="Age" /> </p> <p> <input type="submit" value="Send" /> </p> </form> <script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.5.1.min.js"></script> <script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.17.0/jquery.validate.min.js"></script> <script src="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.10/jquery.validate.unobtrusive.min.js"></script>
И после отправки имени, которое не входит в разрешенный диапазон, мы получим ошибку валидации:
Атрибуты валидации на уровне модели применяются при проверке комбинации свойств. Например, мы не хотим, чтобы имя и пароль пользователя совпадали:
using System.ComponentModel.DataAnnotations; public class NamePasswordEqualAttribute : ValidationAttribute { public NamePasswordEqualAttribute() { ErrorMessage = "Имя и пароль не должны совпадать!"; } public override bool IsValid(object? value) { Person? p = value as Person; return p != null && p.Name != p.Password; }
В данном случае параметр value уже будет представлять всю модель Person в целом. И если переданное значение действительно представляет объект Person и при этом значения его свойств Name и Password не равны, то возвращается true. А это значит, что объект Person прошел валидацию.
И теперь мы можем применить этот атрибут ко всей модели:
[NamePasswordEqual] public class Person { public string? Name { get; set; } public string? Email { get; set; } public string? Password { get; set; } public int Age { get; set; } }
Самовалидация представляет собой процесс, при котором модель запускает механизм валидации из себя самой. И сама инкапсулирует всю логику валидации.
Для этого класс модели должен реализовать интерфейс IValidatableObject:
using System.Collections.Generic; using System.ComponentModel.DataAnnotations; public class Person : IValidatableObject { public string? Name { get; set; } public string? Email { get; set; } public string? Password { get; set; } public int Age { get; set; } public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { var errors = new List<ValidationResult>(); if (string.IsNullOrWhiteSpace(Name)) { errors.Add(new ValidationResult("Введите имя!", new List<string>() { "Name" })); } if (string.IsNullOrWhiteSpace(Email)) { errors.Add(new ValidationResult("Введите электронный адрес!")); } if (Age < 0 || Age > 120) { errors.Add(new ValidationResult("Недопустимый возраст!")); } return errors; } }
В данном случае нам надо реализовать метод Validate() и возвратить коллекцию объектов ValidationResult, которые и будут содержать все ошибки валидации. Если в конструктор ValidationResult передается только сообщение об ошибке, тогда данная ошибка будет относиться ко всей модели в целом. Однако с помощью второго параметра можно указать конкретный список свойств модели, к которым относится ошибка. То есть в данном случае ошибки для свойств Email и Age будут считаться ошибками для всей модели, а ошибка для Name будет ошибкой только этого конкретного свойства: