Проверка модели в ASP.NET Core MVC и Razor pages

В этой статье объясняется, как проверить входные данные пользователей в приложении ASP.NET Core MVC или Razor Pages.

Просмотреть или скачать пример кода (описание скачивания).

Состояние модели

Состояние модели представляет ошибки, создаваемые двумя подсистемами: привязкой модели и проверкой модели. Ошибки привязки модели обычно являются ошибками преобразования данных. Например, в целочисленном поле указывается "x". Проверка модели происходит после ее привязки. В процессе сообщается об ошибках несоответствия данных бизнес-правилам. Например, в поле, которое ожидает оценку от 1 до 5, указывается 0.

Привязка модели и проверка модели выполняются перед выполнением действия контроллера или Razor метода обработчика Pages. Веб-приложение отвечает за проверку ModelState.IsValid и реагирует соответствующим образом. Веб-приложения обычно повторяют страницу с сообщением об ошибке, как показано в следующем Razor примере Pages:

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Для ASP.NET Core MVC с контроллерами и представлениями в следующем примере показано, как проверить ModelState.IsValid внутри действия контроллера:

public async Task<IActionResult> Create(Movie movie)
{
    if (!ModelState.IsValid)
    {
        return View(movie);
    }

    _context.Movies.Add(movie);
    await _context.SaveChangesAsync();

    return RedirectToAction(nameof(Index));
}

Контроллеры веб-API не должны проверять ModelState.IsValid наличие атрибута [ApiController] . В этом случае автоматически возвращается ответ HTTP 400, содержащий сведения об ошибке, если состояние модели недопустимо. Дополнительные сведения см. в разделе Автоматические отклики HTTP 400.

Перезапуск проверки

Проверка выполняется автоматически, однако может потребоваться повторить ее вручную. Например, можно вычислить значение свойства и повторно выполнить проверку после установки свойства в вычисляемое значение. Чтобы повторно выполнить проверку, вызовите ModelStateDictionary.ClearValidationState очистку проверки, относяшейся к модели, за которой следует TryValidateModel:

public async Task<IActionResult> OnPostTryValidateAsync()
{
    var modifiedReleaseDate = DateTime.Now.Date;
    Movie.ReleaseDate = modifiedReleaseDate;

    ModelState.ClearValidationState(nameof(Movie));
    if (!TryValidateModel(Movie, nameof(Movie)))
    {
        return Page();
    }

    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Атрибуты проверки

Атрибуты проверки позволяют задать правила проверки для свойств модели. В следующем примере из примера приложения показан класс модели, который помечается с помощью атрибутов проверки. Атрибут [ClassicMovie] — это настраиваемый атрибут проверки, а другие — встроенные. Не показан [ClassicMovieWithClientValidator], который демонстрирует альтернативный способ реализации настраиваемого атрибута.

public class Movie
{
    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; } = null!;

    [ClassicMovie(1960)]
    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; } = null!;

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }
}

Встроенные атрибуты

Ниже приведены некоторые из встроенных атрибутов проверки.

  • [ValidateNever]: указывает, что свойство или параметр следует исключить из проверки.
  • [Кредитная карта]: проверяет, имеет ли свойство формат кредитной карты. Требуется дополнительный метод проверки jQuery.
  • [Сравнение]: проверяет, соответствуют ли два свойства в модели.
  • [EmailAddress]: проверяет, имеет ли свойство формат электронной почты.
  • [Телефон]: проверяет, имеет ли свойство формат номера телефона.
  • [Диапазон]: проверяет, соответствует ли значение свойства указанному диапазону.
  • [RegularExpression]: проверяет, соответствует ли значение свойства указанному регулярному выражению.
  • [Обязательно]: проверяет, не является ли поле пустым. Дополнительные сведения о поведении этого атрибута см. в разделе Атрибут [Required].
  • [StringLength]: проверяет, что строковое значение свойства не превышает указанное ограничение длины.
  • [URL-адрес]: проверяет, имеет ли свойство формат URL-адреса.
  • [Remote]: проверяет входные данные на клиенте путем вызова метода действия на сервере. Дополнительные сведения о поведении этого атрибута см. в разделе Атрибут [Remote].

Полный список атрибутов проверки можно найти в System.ComponentModel.DataAnnotations пространстве имен.

Сообщения об ошибках

Атрибуты проверки позволяют указать сообщение об ошибке, которое будет отображаться, если входные данные недопустимы. Например:

[StringLength(8, ErrorMessage = "Name length can't be more than 8.")]

На внутреннем уровне атрибуты вызывают String.Format с заполнителем для имени поля и иногда дополнительным заполнителями. Например:

[StringLength(8, ErrorMessage = "{0} length must be between {2} and {1}.", MinimumLength = 6)]

При применении к свойству Name сообщение об ошибке, созданное в приведенном выше коде, имело бы вид Name length must be between 6 and 8 (Длина имени должна быть от 6 до 8).

Чтобы узнать, какие параметры передаются в String.Format для сообщения об ошибке определенного атрибута, см. раздел Исходный код DataAnnotations.

Использование имен свойств JSON в ошибках проверки

По умолчанию при возникновении ошибки проверки в результате проверки модели создается ModelStateDictionary с именем свойства в качестве ключа ошибки. Некоторые приложения, такие как одностраничные приложения, используют имена свойств JSON для ошибок проверки, созданных из веб-API. Следующий код настраивает проверку для использования SystemTextJsonValidationMetadataProvider имен свойств JSON:

using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers(options =>
{
    options.ModelMetadataDetailsProviders.Add(new SystemTextJsonValidationMetadataProvider());
});

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Следующий код настраивает проверку для использования NewtonsoftJsonValidationMetadataProvider имени свойства JSON при использовании Json.NET:

using Microsoft.AspNetCore.Mvc.NewtonsoftJson;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers(options =>
{
    options.ModelMetadataDetailsProviders.Add(new NewtonsoftJsonValidationMetadataProvider());
}).AddNewtonsoftJson();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Пример политики использования верблюдьего регистра см Program.cs . на сайте GitHub.

Непустимые ссылочные типы и атрибут [Обязательный]

Система проверки обрабатывает параметры, не допускающие значения NULL, или привязанные свойства, как если бы они имели [Required(AllowEmptyStrings = true)] атрибут. Включив Nullable контексты, MVC неявно начинает проверку ненулевого свойства или параметров, как если бы они были атрибутом[Required(AllowEmptyStrings = true)]. Рассмотрим следующий код:

public class Person
{
    public string Name { get; set; }
}

Если приложение было создано с <Nullable>enable</Nullable>помощью, отсутствующее значение Name в формате JSON или записи формы приводит к ошибке проверки. Это может показаться противоречивым, так как атрибут подразумевается, но это ожидаемое поведение, так как [Required(AllowEmptyStrings = true)] пустые строки преобразуются в null по умолчанию. Используйте тип ссылки, допускающий значение NULL, чтобы разрешить указывать значения NULL или отсутствующие значения для Name свойства:

public class Person
{
    public string? Name { get; set; }
}

Это поведение можно отключить с помощью параметра SuppressImplicitRequiredAttributeForNonNullableReferenceTypes в Program.cs:

builder.Services.AddControllers(
    options => options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true);

Проверка [Required] на сервере

На сервере обязательное значение считается отсутствующим, если свойство имеет значение NULL. Поле, не допускающее значения NULL, всегда является допустимым, и сообщение об ошибке атрибута [Required] никогда не выводится.

Тем не менее привязка модели для ненулевого свойства может завершиться ошибкой, приводящей к сообщению об ошибке, например The value '' is invalid. Чтобы задать настраиваемое сообщение об ошибке во время проверки не допускающих значения NULL типов на стороне сервера, у вас есть следующие варианты.

  • Сделать поле допускающим значение NULL (например, decimal? вместо decimal). Типы значений,> допускающие<значение NULL, рассматриваются как стандартные типы, допускающие значение NULL.

  • Указать сообщение об ошибке по умолчанию для использования в привязке модели, как показано в следующем примере.

    builder.Services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    builder.Services.AddSingleton
        <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();
    

    Дополнительные сведения об ошибках привязки модели, у которых можно задать сообщение по умолчанию, см. в разделе DefaultModelBindingMessageProvider.

Проверка [Required] на клиенте

Типы и строки, не допускающие значение NULL, обрабатываются на клиенте не так, как на сервере. На клиенте:

  • Значение считается присутствующим только в том случае, если для него вводятся данные. Таким образом, проверка на стороне клиента обрабатывает типы, не допускающие значение NULL, так же, как обнуляемые типы.
  • Пробелы в поле строки считаются допустимыми входными данными при проверке методом jQuery required. Проверка на стороне сервера считает, что обязательное строковое поле недопустимо, если введены только пробелы.

Как отмечалось ранее, не допускающие значение NULL типы рассматриваются как имеющие атрибут [Required(AllowEmptyStrings = true)]. Это означает, что вы получаете проверку на стороне клиента, даже если не применять атрибут [Required(AllowEmptyStrings = true)]. Но если вы не используете атрибут, вы получаете сообщение об ошибке по умолчанию. Чтобы задать настраиваемое сообщение об ошибке, используйте атрибут.

Атрибут [Remote]

Атрибут [Remote] реализует проверку на стороне клиента, требующую вызова метода на сервере для определения допустимости входных данных поля. Например, приложению может потребоваться проверить, занято ли имя пользователя.

Для реализации удаленной проверки сделайте следующее.

  1. Создайте для вызова из JavaScript метод действия. Удаленный метод проверки jQuery ожидает ответ JSON:

    • true означает, что входные данные допустимы.
    • false, undefined или null означают, что входные данные недопустимы. Вывод стандартного сообщения об ошибке
    • Все прочие значения означают, что входные данные недопустимы. Вывод строки как настраиваемого сообщения об ошибке.

    Ниже приведен пример метода действия, который возвращает настраиваемое сообщение об ошибке.

    [AcceptVerbs("GET", "POST")]
    public IActionResult VerifyEmail(string email)
    {
        if (!_userService.VerifyEmail(email))
        {
            return Json($"Email {email} is already in use.");
        }
    
        return Json(true);
    }
    
  2. В классе модели пометьте свойство атрибутом [Remote], указывающим метод действия проверки, как показано в следующем примере.

    [Remote(action: "VerifyEmail", controller: "Users")]
    public string Email { get; set; } = null!;
    

Кроме того, необходимо реализовать проверку на стороне сервера для клиентов, которые отключили JavaScript.

Дополнительные поля

Свойство AdditionalFields атрибута [Remote] позволяет проверять сочетания полей с данными на сервере. Например, если бы в модели User были свойства FirstName и LastName, могла бы возникнуть необходимость проверить, нет ли уже пользователя с такой парой имен. В следующем примере показано использование AdditionalFields.

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(LastName))]
[Display(Name = "First Name")]
public string FirstName { get; set; } = null!;

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName))]
[Display(Name = "Last Name")]
public string LastName { get; set; } = null!;

AdditionalFields можно явно присвоить строкам "FirstName" и "LastName", но использование оператора nameof упрощает дальнейший рефакторинг. Методу действия для этой проверки необходимо принимать аргументы firstName и lastName

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyName(string firstName, string lastName)
{
    if (!_userService.VerifyName(firstName, lastName))
    {
        return Json($"A user named {firstName} {lastName} already exists.");
    }

    return Json(true);
}

Когда пользователь вводит имя или фамилию, JavaScript выполняет удаленный вызов, чтобы увидеть, будет ли эта пара принята.

Чтобы проверить несколько дополнительных полей, их следует указывать в виде списка с разделителями-запятыми. Например, чтобы добавить в модель свойство MiddleName, задайте атрибут [Remote], как показано в следующем примере.

[Remote(action: "VerifyName", controller: "Users",
    AdditionalFields = nameof(FirstName) + "," + nameof(LastName))]
public string MiddleName { get; set; }

AdditionalFields, как и все аргументы атрибутов, должен представлять собой константное выражение. Поэтому не следует использовать интерполированную строку или вызов Joinдля инициализации AdditionalFields.

Альтернативы для встроенных атрибутов

Если вам нужна проверка, которую не предоставляют встроенные атрибуты, вы можете следующее.

Настраиваемые атрибуты

Для сценариев, где не годятся встроенные атрибуты проверки, можно создать настраиваемые атрибуты. Создайте класс, наследуемый от ValidationAttribute, и переопределите метод IsValid.

Метод IsValid принимает объект с именем value, который является входными данными для проверки. Перегрузка также принимает объект ValidationContext, который предоставляет дополнительные сведения, такие как экземпляр модели, созданный с помощью привязки модели.

В следующем примере проверяется, что дата выпуска фильмов в классическом жанре задана не позднее указанного года. Атрибут [ClassicMovie]:

  • выполняется только на сервере.
  • Для классических фильмов проверяет дату выпуска:
public class ClassicMovieAttribute : ValidationAttribute
{
    public ClassicMovieAttribute(int year)
        => Year = year;

    public int Year { get; }

    public string GetErrorMessage() =>
        $"Classic movies must have a release year no later than {Year}.";

    protected override ValidationResult? IsValid(
        object? value, ValidationContext validationContext)
    {
        var movie = (Movie)validationContext.ObjectInstance;
        var releaseYear = ((DateTime)value!).Year;

        if (movie.Genre == Genre.Classic && releaseYear > Year)
        {
            return new ValidationResult(GetErrorMessage());
        }

        return ValidationResult.Success;
    }
}

Приведенная выше переменная movie представляет объект Movie, который содержит данные из переданной формы. Если проверка завершается неудачно, возвращается ValidationResult с сообщением об ошибке.

IValidatableObject

Предыдущий пример работает только с типами Movie. Другой вариант для проверки на уровне класса — реализация IValidatableObject в классе модели, как показано в следующем примере.

public class ValidatableMovie : IValidatableObject
{
    private const int _classicYear = 1960;

    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; } = null!;

    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; } = null!;

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Genre == Genre.Classic && ReleaseDate.Year > _classicYear)
        {
            yield return new ValidationResult(
                $"Classic movies must have a release year no later than {_classicYear}.",
                new[] { nameof(ReleaseDate) });
        }
    }
}

Пользовательская проверка

В следующем коде показано, как добавить ошибку модели после изучения модели:

if (Contact.Name == Contact.ShortName)
{
    ModelState.AddModelError("Contact.ShortName", 
                             "Short name can't be the same as Name.");
}

Следующий код реализует тест проверки в контроллере:

if (contact.Name == contact.ShortName)
{
    ModelState.AddModelError(nameof(contact.ShortName),
                             "Short name can't be the same as Name.");
}

Следующий код проверяет номер телефона и электронную почту уникальными:

public async Task<IActionResult> OnPostAsync()
{
    // Attach Validation Error Message to the Model on validation failure.          

    if (Contact.Name == Contact.ShortName)
    {
        ModelState.AddModelError("Contact.ShortName", 
                                 "Short name can't be the same as Name.");
    }

    if (_context.Contact.Any(i => i.PhoneNumber == Contact.PhoneNumber))
    {
        ModelState.AddModelError("Contact.PhoneNumber",
                                  "The Phone number is already in use.");
    }
    if (_context.Contact.Any(i => i.Email == Contact.Email))
    {
        ModelState.AddModelError("Contact.Email", "The Email is already in use.");
    }

    if (!ModelState.IsValid || _context.Contact == null || Contact == null)
    {
        // if model is invalid, return the page with the model state errors.
        return Page();
    }
    _context.Contact.Add(Contact);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Следующий код реализует тест проверки в контроллере:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,Name,ShortName,Email,PhoneNumber")] Contact contact)
{
    // Attach Validation Error Message to the Model on validation failure.
    if (contact.Name == contact.ShortName)
    {
        ModelState.AddModelError(nameof(contact.ShortName),
                                 "Short name can't be the same as Name.");
    }

    if (_context.Contact.Any(i => i.PhoneNumber == contact.PhoneNumber))
    {
        ModelState.AddModelError(nameof(contact.PhoneNumber),
                                  "The Phone number is already in use.");
    }
    if (_context.Contact.Any(i => i.Email == contact.Email))
    {
        ModelState.AddModelError(nameof(contact.Email), "The Email is already in use.");
    }

    if (ModelState.IsValid)
    {
        _context.Add(contact);
        await _context.SaveChangesAsync();
        return RedirectToAction(nameof(Index));
    }
    return View(contact);
}

Проверка уникального номера телефона или электронной почты обычно выполняется с помощью удаленной проверки.

ValidationResult

Рассмотрим следующее настраиваемое:ValidateNameAttribute

public class ValidateNameAttribute : ValidationAttribute
{
    public ValidateNameAttribute()
    {
        const string defaultErrorMessage = "Error with Name";
        ErrorMessage ??= defaultErrorMessage;
    }

    protected override ValidationResult? IsValid(object? value,
                                         ValidationContext validationContext)
    {
        if (value == null || string.IsNullOrWhiteSpace(value.ToString()))
        {
            return new ValidationResult("Name is required.");
        }

        if (value.ToString()!.ToLower().Contains("zz"))
        {

            return new ValidationResult(
                        FormatErrorMessage(validationContext.DisplayName));
        }

        return ValidationResult.Success;
    }
}

В следующем коде применяется пользовательский [ValidateName] атрибут:

public class Contact
{
    public Guid Id { get; set; }

    [ValidateName(ErrorMessage = "Name must not contain `zz`")] 
    public string? Name { get; set; }
    public string? Email { get; set; }
    public string? PhoneNumber { get; set; }
}

Когда модель содержит zz, возвращается новое ValidationResult .

Проверка узлов верхнего уровня

Узлы верхнего уровня содержат:

  • Параметры действия
  • свойства контроллера;
  • параметры обработчика страниц;
  • свойства страничной модели.

Проверка привязанных к модели узлов верхнего уровня осуществляется наряду с проверкой свойств модели. В следующем примере из примера приложенияVerifyPhone метод использует RegularExpressionAttribute метод для проверки phone параметра действия:

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyPhone(
    [RegularExpression(@"^\d{3}-\d{3}-\d{4}$")] string phone)
{
    if (!ModelState.IsValid)
    {
        return Json($"Phone {phone} has an invalid format. Format: ###-###-####");
    }

    return Json(true);
}

Узлы верхнего уровня могут применять класс BindRequiredAttribute с атрибутами проверки. В следующем примере из примера приложения метод указывает, CheckAge что age параметр должен быть привязан из строки запроса при отправке формы:

[HttpPost]
public IActionResult CheckAge([BindRequired, FromQuery] int age)
{

На странице "Контрольный возраст" (CheckAge.cshtml) есть две формы. Первая форма отправляет значение Age, равное 99, в виде параметра строки запроса: https://localhost:5001/Users/CheckAge?Age=99.

Если из строки запроса отправлен параметр age в правильном формате, форма проходит проверку.

Вторая форма на странице "Check Age" (Проверка возраста) отправляет значение Age в теле запроса, и проверка завершается сбоем. Ошибка привязки связана с тем, что параметр age должен поступать из строки запроса.

Максимальное количество ошибок

При достижении максимального количества ошибок (по умолчанию 200) проверка прекращается. Это число можно изменить с помощью следующего кода в Program.cs:

builder.Services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.MaxModelValidationErrors = 50;
        options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
            _ => "The field is required.");
    });

builder.Services.AddSingleton
    <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();

Максимальная рекурсия

ValidationVisitor проходит через граф объектов в проверяемой модели. У глубоких моделей, содержащих бесконечную рекурсию, в ходе проверки может произойти переполнение стека. MvcOptions.MaxValidationDepth предоставляет способ остановить проверку рано, если рекурсия посетителя превышает настроенную глубину. Значение MvcOptions.MaxValidationDepth по умолчанию —32.

Автоматическое сокращение

Проверка автоматически сокращается (пропускается), если граф модели не требует проверки. К числу объектов, которые среда выполнения пропускает при проверке, относятся коллекции примитивов (такие как byte[], string[], Dictionary<string, string>) и сложные графы объектов, которые не имеют проверяющих элементов управления.

Проверка на стороне клиента

Проверка на стороне клиента не позволяет отправлять форму, пока ее данные не будут допустимыми. При нажатии кнопки "Отправить" выполняется код JavaScript, который либо отправляет форму, либо выводит сообщения об ошибках.

Проверка на стороне клиента позволяет избежать ненужного кругового захода на сервер при наличии ошибки ввода в форме. Следующие ссылки на скрипты и _Layout.cshtml _ValidationScriptsPartial.cshtml поддерживают проверку на стороне клиента:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/jquery.validate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.12/jquery.validate.unobtrusive.js"></script>

Скрипт проверки jQuery Unobtrusive Validation — это пользовательская интерфейсная библиотека Майкрософт, которая основана на популярном подключаемом модуле проверки jQuery. Без скрипта ненавязчивой проверки jQuery одну и ту же логику проверки приходилось бы реализовывать в двух местах: в атрибутах проверки для свойств модели на стороне сервера, а затем еще раз в скриптах на стороне клиента. Вместо этого вспомогательные функции тегов и вспомогательные функции HTML могут использовать атрибуты проверки и метаданные типов из свойств модели для обработки атрибутов data- HTML 5 в элементах форм, требующих проверки. JQuery Untrusive Validation анализирует data- атрибуты и передает логику в jQuery Validation, эффективно копируя логику проверки на стороне сервера клиенту. Ошибки проверки могут выводиться в клиенте с помощью вспомогательных функций тегов, как показано ниже.

<div class="form-group">
    <label asp-for="Movie.ReleaseDate" class="control-label"></label>
    <input asp-for="Movie.ReleaseDate" class="form-control" />
    <span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
</div>

Приведенные выше вспомогательные функции тегов отрисовывают следующий код HTML:

<div class="form-group">
    <label class="control-label" for="Movie_ReleaseDate">Release Date</label>
    <input class="form-control" type="date" data-val="true"
        data-val-required="The Release Date field is required."
        id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">
    <span class="text-danger field-validation-valid"
        data-valmsg-for="Movie.ReleaseDate" data-valmsg-replace="true"></span>
</div>

Обратите внимание на то, что атрибуты data- в выходных данных HTML соответствуют атрибутам проверки для свойства Movie.ReleaseDate. Атрибут data-val-required содержит сообщение об ошибке, которое выводится, если пользователь не заполнил поле даты выхода. jQuery Unobtrusive Validation передает это значение методу jQuery Validation required(), который затем отображает это сообщение в сопровождающем <элементе диапазона> .

Проверка типов данных основана на типе свойства .NET, если только это не переопределяется атрибутом [DataType] . Браузеры имеют свои сообщения об по умолчанию, но пакет ненавязчивой проверки jQuery может переопределять эти сообщения. [DataType] атрибуты и подклассы, такие как [EmailAddress] , позволяют указать сообщение об ошибке.

Ненавязчивая проверка

См. сведения о ненавязчивой проверке в этой проблеме GitHub.

Добавление проверки к динамическим формам

JQuery Unobtrusive Validation передает логику проверки и параметры jQuery Validation при первой загрузке страницы. Поэтому динамически создаваемые формы не подвергаются проверке автоматически. Чтобы включить проверку, необходимо указать, что скрипт ненавязчивой проверки jQuery должен анализировать динамическую форму сразу после ее создания. Например, приведенный ниже код показывает, как можно настроить проверку на стороне клиента для формы, добавленной посредством AJAX.

$.get({
    url: "https://url/that/returns/a/form",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add form. " + errorThrown);
    },
    success: function(newFormHTML) {
        var container = document.getElementById("form-container");
        container.insertAdjacentHTML("beforeend", newFormHTML);
        var forms = container.getElementsByTagName("form");
        var newForm = forms[forms.length - 1];
        $.validator.unobtrusive.parse(newForm);
    }
})

Метод $.validator.unobtrusive.parse() принимает селектор jQuery в качестве единственного аргумента. Этот метод предписывает скрипту ненавязчивой проверки jQuery анализировать атрибуты data- форм в этом селекторе. Затем значения этих атрибутов передаются в подключаемый модуль проверки jQuery.

Добавление проверки к динамическим элементам управления

Метод $.validator.unobtrusive.parse() обрабатывает всю форму, а не отдельные динамически создаваемые элементы управления, такие как <input> и <select/>. Для повторной обработки формы удалите данные проверки, которые были добавлены при анализе формы ранее, как показано в следующем примере:

$.get({
    url: "https://url/that/returns/a/control",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add control. " + errorThrown);
    },
    success: function(newInputHTML) {
        var form = document.getElementById("my-form");
        form.insertAdjacentHTML("beforeend", newInputHTML);
        $(form).removeData("validator")    // Added by jQuery Validation
               .removeData("unobtrusiveValidation");   // Added by jQuery Unobtrusive Validation
        $.validator.unobtrusive.parse(form);
    }
})

Настраиваемая проверка на стороне клиента

Настраиваемая проверка на стороне клиента выполняется путем data- создания HTML-атрибутов, работающих с пользовательским адаптером проверки jQuery. В следующем примере кода адаптера используются атрибуты [ClassicMovie] и [ClassicMovieWithClientValidator], которые были введены ранее в этой статье.

$.validator.addMethod('classicmovie', function (value, element, params) {
    var genre = $(params[0]).val(), year = params[1], date = new Date(value);

    // The Classic genre has a value of '0'.
    if (genre && genre.length > 0 && genre[0] === '0') {
        // The release date for a Classic is valid if it's no greater than the given year.
        return date.getUTCFullYear() <= year;
    }

    return true;
});

$.validator.unobtrusive.adapters.add('classicmovie', ['year'], function (options) {
    var element = $(options.form).find('select#Movie_Genre')[0];

    options.rules['classicmovie'] = [element, parseInt(options.params['year'])];
    options.messages['classicmovie'] = options.message;
});

Сведения о том, как записывать адаптеры, см. в документации по проверке jQuery.

Использование адаптера для заданного поля инициируется атрибутами data-, которые:

  • помечают поле как проходящее проверку (data-val="true");
  • указывают имя правила проверки и текст сообщения об ошибке (например, data-val-rulename="Error message.");
  • указывают любые дополнительные параметры, в которых нуждается проверяющий элемент управления (например, data-val-rulename-param1="value").

В следующем примере показаны data- атрибуты атрибута ClassicMovie примера приложения:

<input class="form-control" type="date"
    data-val="true"
    data-val-classicmovie="Classic movies must have a release year no later than 1960."
    data-val-classicmovie-year="1960"
    data-val-required="The Release Date field is required."
    id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">

Как отмечалось ранее, вспомогательные функции тегов и вспомогательные методы HTML используют сведения из атрибутов проверки для подготовки к отрисовке атрибутов data-. Существует два варианта для написания кода, который приводит к созданию настраиваемых атрибутов HTML data-.

  • Создайте класс, производный от AttributeAdapterBase<TAttribute>, и класс, реализующий IValidationAttributeAdapterProvider; зарегистрируйте атрибут и его адаптер в функции внедрения зависимостей. Этот метод следует единому принципу ответственности в коде проверки, связанном с сервером, и коде проверки, связанном с клиентом, в отдельных классах. Адаптер также имеет преимущество, так как он зарегистрирован в DI, другие службы в DI доступны для него при необходимости.
  • Реализуйте IClientModelValidator в вашем классе ValidationAttribute. Этот метод стоит использовать, если атрибут не выполняет проверку на стороне сервера и не нуждается в службах из функции внедрения зависимостей.

AttributeAdapter для проверки на стороне клиента

Этот метод отрисовки data- атрибутов в HTML используется атрибутом ClassicMovie в примере приложения. Чтобы добавить клиентскую проверку с помощью этого метода, сделайте следующее.

  1. Создайте класс адаптера атрибута для настраиваемого атрибута проверки. Создайте класс, производный от AttributeAdapterBase<TAttribute>. Создайте метод AddValidation, который добавляет атрибуты data- для выводимых данных, как показано в следующем примере.

    public class ClassicMovieAttributeAdapter : AttributeAdapterBase<ClassicMovieAttribute>
    {
        public ClassicMovieAttributeAdapter(
            ClassicMovieAttribute attribute, IStringLocalizer? stringLocalizer)
            : base(attribute, stringLocalizer)
        {
    
        }
    
        public override void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage(context));
    
            var year = Attribute.Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
    
        public override string GetErrorMessage(ModelValidationContextBase validationContext)
            => Attribute.GetErrorMessage();
    }
    
  2. Создайте класс поставщика адаптера, который реализует IValidationAttributeAdapterProvider. В методе GetAttributeAdapter передайте настраиваемый атрибут в конструктор адаптера, как показано в следующем примере.

    public class CustomValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider
    {
        private readonly IValidationAttributeAdapterProvider baseProvider =
            new ValidationAttributeAdapterProvider();
    
        public IAttributeAdapter? GetAttributeAdapter(
            ValidationAttribute attribute, IStringLocalizer? stringLocalizer)
        {
            if (attribute is ClassicMovieAttribute classicMovieAttribute)
            {
                return new ClassicMovieAttributeAdapter(classicMovieAttribute, stringLocalizer);
            }
    
            return baseProvider.GetAttributeAdapter(attribute, stringLocalizer);
        }
    }
    
  3. Зарегистрируйте поставщик адаптера для внедрения зависимостей в Program.cs.

    builder.Services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    builder.Services.AddSingleton
        <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();
    

IClientModelValidator для проверки на стороне клиента

Этот метод отрисовки data- атрибутов в HTML используется атрибутом ClassicMovieWithClientValidator в примере приложения. Чтобы добавить клиентскую проверку с помощью этого метода, сделайте следующее.

  • В настраиваемом атрибуте проверки реализуйте интерфейс IClientModelValidator и создайте метод AddValidation. В метод AddValidation добавьте атрибуты data- для проверки, как показано в следующем примере.

    public class ClassicMovieWithClientValidatorAttribute :
        ValidationAttribute, IClientModelValidator
    {
        public ClassicMovieWithClientValidatorAttribute(int year)
            => Year = year;
    
        public int Year { get; }
    
        public void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage());
    
            var year = Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
    
        public string GetErrorMessage() =>
            $"Classic movies must have a release year no later than {Year}.";
    
        protected override ValidationResult? IsValid(
            object? value, ValidationContext validationContext)
        {
            var movie = (Movie)validationContext.ObjectInstance;
            var releaseYear = ((DateTime)value!).Year;
    
            if (movie.Genre == Genre.Classic && releaseYear > Year)
            {
                return new ValidationResult(GetErrorMessage());
            }
    
            return ValidationResult.Success;
        }
    
        private static bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
        {
            if (attributes.ContainsKey(key))
            {
                return false;
            }
    
            attributes.Add(key, value);
            return true;
        }
    }
    

Отключение проверки на стороне клиента

Следующий код отключает проверку клиента в Razor Pages:

builder.Services.AddRazorPages()
    .AddViewOptions(options =>
    {
        options.HtmlHelperOptions.ClientValidationEnabled = false;
    });

Другие параметры отключения проверки на стороне клиента:

  • Закомментируйте ссылку _ValidationScriptsPartial на все .cshtml файлы.
  • Удалите содержимое файла Pages\Shared_ValidationScriptsPartial.cshtml .

Приведенный выше подход не предотвращает проверку на стороне клиента библиотеки классов ASP.NET Core IdentityRazor . Дополнительные сведения см. в разделе Шаблоны Identity в проектах ASP.NET Core.

Сведения о проблеме

Сведения о проблеме — это не единственный формат ответа, описывающий ошибку API HTTP, однако они часто используются для сообщения об ошибках для API HTTP.

Служба сведений о проблеме IProblemDetailsService реализует интерфейс, который поддерживает создание сведений о проблеме в ASP.NET Core. Метод AddProblemDetails(IServiceCollection) расширения для IServiceCollection регистрации реализации по умолчанию IProblemDetailsService .

В приложениях ASP.NET Core следующий по промежуточному слоя создает ответы HTTP для получения сведений о проблемах при AddProblemDetails вызове, за исключением случаев, когда Accept заголовок HTTP запроса не включает один из типов контента, поддерживаемых зарегистрированным IProblemDetailsWriter (по умолчанию: application/json):

  • ExceptionHandlerMiddleware: создает ответ сведений о проблеме, когда настраиваемый обработчик не определен.
  • StatusCodePagesMiddleware: создает ответ сведений о проблеме по умолчанию.
  • DeveloperExceptionPageMiddleware: создает ответ сведений о проблеме в разработке, если Accept заголовок HTTP запроса не включает text/html.

Дополнительные ресурсы

В этой статье объясняется, как проверить входные данные пользователей в приложении ASP.NET Core MVC или Razor Pages.

Просмотреть или скачать пример кода (описание скачивания).

Состояние модели

Состояние модели представляет ошибки, создаваемые двумя подсистемами: привязкой модели и проверкой модели. Ошибки привязки модели обычно являются ошибками преобразования данных. Например, в целочисленном поле указывается "x". Проверка модели происходит после ее привязки. В процессе сообщается об ошибках несоответствия данных бизнес-правилам. Например, в поле, которое ожидает оценку от 1 до 5, указывается 0.

Привязка модели и проверка модели выполняются перед выполнением действия контроллера или Razor метода обработчика Pages. Веб-приложение отвечает за проверку ModelState.IsValid и реагирует соответствующим образом. Веб-приложения обычно повторяют страницу с сообщением об ошибке, как показано в следующем Razor примере Pages:

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Для ASP.NET Core MVC с контроллерами и представлениями в следующем примере показано, как проверить ModelState.IsValid внутри действия контроллера:

public async Task<IActionResult> Create(Movie movie)
{
    if (!ModelState.IsValid)
    {
        return View(movie);
    }

    _context.Movies.Add(movie);
    await _context.SaveChangesAsync();

    return RedirectToAction(nameof(Index));
}

Контроллеры веб-API не должны проверять ModelState.IsValid наличие атрибута [ApiController] . В этом случае автоматически возвращается ответ HTTP 400, содержащий сведения об ошибке, если состояние модели недопустимо. Дополнительные сведения см. в разделе Автоматические отклики HTTP 400.

Перезапуск проверки

Проверка выполняется автоматически, однако может потребоваться повторить ее вручную. Например, можно вычислить значение свойства и повторно выполнить проверку после установки свойства в вычисляемое значение. Чтобы повторно выполнить проверку, вызовите ModelStateDictionary.ClearValidationState очистку проверки, относяшейся к модели, за которой следует TryValidateModel:

public async Task<IActionResult> OnPostTryValidateAsync()
{
    var modifiedReleaseDate = DateTime.Now.Date;
    Movie.ReleaseDate = modifiedReleaseDate;

    ModelState.ClearValidationState(nameof(Movie));
    if (!TryValidateModel(Movie, nameof(Movie)))
    {
        return Page();
    }

    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Атрибуты проверки

Атрибуты проверки позволяют задать правила проверки для свойств модели. В следующем примере из примера приложения показан класс модели, который помечается с помощью атрибутов проверки. Атрибут [ClassicMovie] — это настраиваемый атрибут проверки, а другие — встроенные. Не показан [ClassicMovieWithClientValidator], который демонстрирует альтернативный способ реализации настраиваемого атрибута.

public class Movie
{
    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; } = null!;

    [ClassicMovie(1960)]
    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; } = null!;

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }
}

Встроенные атрибуты

Ниже приведены некоторые из встроенных атрибутов проверки.

  • [ValidateNever]: указывает, что свойство или параметр следует исключить из проверки.
  • [Кредитная карта]: проверяет, имеет ли свойство формат кредитной карты. Требуется дополнительный метод проверки jQuery.
  • [Сравнение]: проверяет, соответствуют ли два свойства в модели.
  • [EmailAddress]: проверяет, имеет ли свойство формат электронной почты.
  • [Телефон]: проверяет, имеет ли свойство формат номера телефона.
  • [Диапазон]: проверяет, соответствует ли значение свойства указанному диапазону.
  • [RegularExpression]: проверяет, соответствует ли значение свойства указанному регулярному выражению.
  • [Обязательно]: проверяет, не является ли поле пустым. Дополнительные сведения о поведении этого атрибута см. в разделе Атрибут [Required].
  • [StringLength]: проверяет, что строковое значение свойства не превышает указанное ограничение длины.
  • [URL-адрес]: проверяет, имеет ли свойство формат URL-адреса.
  • [Remote]: проверяет входные данные на клиенте путем вызова метода действия на сервере. Дополнительные сведения о поведении этого атрибута см. в разделе Атрибут [Remote].

Полный список атрибутов проверки можно найти в System.ComponentModel.DataAnnotations пространстве имен.

Сообщения об ошибках

Атрибуты проверки позволяют указать сообщение об ошибке, которое будет отображаться, если входные данные недопустимы. Например:

[StringLength(8, ErrorMessage = "Name length can't be more than 8.")]

На внутреннем уровне атрибуты вызывают String.Format с заполнителем для имени поля и иногда дополнительным заполнителями. Например:

[StringLength(8, ErrorMessage = "{0} length must be between {2} and {1}.", MinimumLength = 6)]

При применении к свойству Name сообщение об ошибке, созданное в приведенном выше коде, имело бы вид Name length must be between 6 and 8 (Длина имени должна быть от 6 до 8).

Чтобы узнать, какие параметры передаются в String.Format для сообщения об ошибке определенного атрибута, см. раздел Исходный код DataAnnotations.

Непустимые ссылочные типы и атрибут [Обязательный]

Система проверки обрабатывает параметры, не допускающие значения NULL, или привязанные свойства, как если бы они имели [Required(AllowEmptyStrings = true)] атрибут. Включив Nullable контексты, MVC неявно начинает проверку свойств, не допускающих значение NULL, для не универсальных типов или параметров, как если бы они были атрибутом[Required(AllowEmptyStrings = true)]. Рассмотрим следующий код:

public class Person
{
    public string Name { get; set; }
}

Если приложение было создано с <Nullable>enable</Nullable>помощью, отсутствующее значение Name в формате JSON или записи формы приводит к ошибке проверки. Используйте тип ссылки, допускающий значение NULL, чтобы разрешить указывать значения NULL или отсутствующие значения для Name свойства:

public class Person
{
    public string? Name { get; set; }
}

Это поведение можно отключить с помощью параметра SuppressImplicitRequiredAttributeForNonNullableReferenceTypes в Program.cs:

builder.Services.AddControllers(
    options => options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true);

Свойства, не допускающие значения NULL для универсальных типов и атрибута [Обязательный]

Свойства, не допускающие значение NULL для универсальных типов, должны включать [Required] атрибут, если этот тип является обязательным. В следующем коде TestRequired не требуется:

public class WeatherForecast<T>
{
    public string TestRequired { get; set; } = null!;
    public T? Inner { get; set; }
}

В следующем коде TestRequired явно помечается как обязательный:

using System.ComponentModel.DataAnnotations;

public class WeatherForecast<T>
{
    [Required]
    public string TestRequired { get; set; } = null!;
    public T? Inner { get; set; }
}

Проверка [Required] на сервере

На сервере обязательное значение считается отсутствующим, если свойство имеет значение NULL. Поле, не допускающее значения NULL, всегда является допустимым, и сообщение об ошибке атрибута [Required] никогда не выводится.

Тем не менее привязка модели для ненулевого свойства может завершиться ошибкой, приводящей к сообщению об ошибке, например The value '' is invalid. Чтобы задать настраиваемое сообщение об ошибке во время проверки не допускающих значения NULL типов на стороне сервера, у вас есть следующие варианты.

  • Сделать поле допускающим значение NULL (например, decimal? вместо decimal). Типы значений,> допускающие<значение NULL, рассматриваются как стандартные типы, допускающие значение NULL.

  • Указать сообщение об ошибке по умолчанию для использования в привязке модели, как показано в следующем примере.

    builder.Services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    builder.Services.AddSingleton
        <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();
    

    Дополнительные сведения об ошибках привязки модели, у которых можно задать сообщение по умолчанию, см. в разделе DefaultModelBindingMessageProvider.

Проверка [Required] на клиенте

Типы и строки, не допускающие значение NULL, обрабатываются на клиенте не так, как на сервере. На клиенте:

  • Значение считается присутствующим только в том случае, если для него вводятся данные. Таким образом, проверка на стороне клиента обрабатывает типы, не допускающие значение NULL, так же, как обнуляемые типы.
  • Пробелы в поле строки считаются допустимыми входными данными при проверке методом jQuery required. Проверка на стороне сервера считает, что обязательное строковое поле недопустимо, если введены только пробелы.

Как отмечалось ранее, не допускающие значение NULL типы рассматриваются как имеющие атрибут [Required(AllowEmptyStrings = true)]. Это означает, что вы получаете проверку на стороне клиента, даже если не применять атрибут [Required(AllowEmptyStrings = true)]. Но если вы не используете атрибут, вы получаете сообщение об ошибке по умолчанию. Чтобы задать настраиваемое сообщение об ошибке, используйте атрибут.

Атрибут [Remote]

Атрибут [Remote] реализует проверку на стороне клиента, требующую вызова метода на сервере для определения допустимости входных данных поля. Например, приложению может потребоваться проверить, занято ли имя пользователя.

Для реализации удаленной проверки сделайте следующее.

  1. Создайте для вызова из JavaScript метод действия. Удаленный метод проверки jQuery ожидает ответ JSON:

    • true означает, что входные данные допустимы.
    • false, undefined или null означают, что входные данные недопустимы. Вывод стандартного сообщения об ошибке
    • Все прочие значения означают, что входные данные недопустимы. Вывод строки как настраиваемого сообщения об ошибке.

    Ниже приведен пример метода действия, который возвращает настраиваемое сообщение об ошибке.

    [AcceptVerbs("GET", "POST")]
    public IActionResult VerifyEmail(string email)
    {
        if (!_userService.VerifyEmail(email))
        {
            return Json($"Email {email} is already in use.");
        }
    
        return Json(true);
    }
    
  2. В классе модели пометьте свойство атрибутом [Remote], указывающим метод действия проверки, как показано в следующем примере.

    [Remote(action: "VerifyEmail", controller: "Users")]
    public string Email { get; set; } = null!;
    

Дополнительные поля

Свойство AdditionalFields атрибута [Remote] позволяет проверять сочетания полей с данными на сервере. Например, если бы в модели User были свойства FirstName и LastName, могла бы возникнуть необходимость проверить, нет ли уже пользователя с такой парой имен. В следующем примере показано использование AdditionalFields.

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(LastName))]
[Display(Name = "First Name")]
public string FirstName { get; set; } = null!;

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName))]
[Display(Name = "Last Name")]
public string LastName { get; set; } = null!;

AdditionalFields можно явно присвоить строкам "FirstName" и "LastName", но использование оператора nameof упрощает дальнейший рефакторинг. Методу действия для этой проверки необходимо принимать аргументы firstName и lastName

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyName(string firstName, string lastName)
{
    if (!_userService.VerifyName(firstName, lastName))
    {
        return Json($"A user named {firstName} {lastName} already exists.");
    }

    return Json(true);
}

Когда пользователь вводит имя или фамилию, JavaScript выполняет удаленный вызов, чтобы увидеть, будет ли эта пара принята.

Чтобы проверить несколько дополнительных полей, их следует указывать в виде списка с разделителями-запятыми. Например, чтобы добавить в модель свойство MiddleName, задайте атрибут [Remote], как показано в следующем примере.

[Remote(action: "VerifyName", controller: "Users",
    AdditionalFields = nameof(FirstName) + "," + nameof(LastName))]
public string MiddleName { get; set; }

AdditionalFields, как и все аргументы атрибутов, должен представлять собой константное выражение. Поэтому не следует использовать интерполированную строку или вызов Joinдля инициализации AdditionalFields.

Альтернативы для встроенных атрибутов

Если вам нужна проверка, которую не предоставляют встроенные атрибуты, вы можете следующее.

Настраиваемые атрибуты

Для сценариев, где не годятся встроенные атрибуты проверки, можно создать настраиваемые атрибуты. Создайте класс, наследуемый от ValidationAttribute, и переопределите метод IsValid.

Метод IsValid принимает объект с именем value, который является входными данными для проверки. Перегрузка также принимает объект ValidationContext, который предоставляет дополнительные сведения, такие как экземпляр модели, созданный с помощью привязки модели.

В следующем примере проверяется, что дата выпуска фильмов в классическом жанре задана не позднее указанного года. Атрибут [ClassicMovie]:

  • выполняется только на сервере.
  • Для классических фильмов проверяет дату выпуска:
public class ClassicMovieAttribute : ValidationAttribute
{
    public ClassicMovieAttribute(int year)
        => Year = year;

    public int Year { get; }

    public string GetErrorMessage() =>
        $"Classic movies must have a release year no later than {Year}.";

    protected override ValidationResult? IsValid(
        object? value, ValidationContext validationContext)
    {
        var movie = (Movie)validationContext.ObjectInstance;
        var releaseYear = ((DateTime)value!).Year;

        if (movie.Genre == Genre.Classic && releaseYear > Year)
        {
            return new ValidationResult(GetErrorMessage());
        }

        return ValidationResult.Success;
    }
}

Приведенная выше переменная movie представляет объект Movie, который содержит данные из переданной формы. Если проверка завершается неудачно, возвращается ValidationResult с сообщением об ошибке.

IValidatableObject

Предыдущий пример работает только с типами Movie. Другой вариант для проверки на уровне класса — реализация IValidatableObject в классе модели, как показано в следующем примере.

public class ValidatableMovie : IValidatableObject
{
    private const int _classicYear = 1960;

    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; } = null!;

    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; } = null!;

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Genre == Genre.Classic && ReleaseDate.Year > _classicYear)
        {
            yield return new ValidationResult(
                $"Classic movies must have a release year no later than {_classicYear}.",
                new[] { nameof(ReleaseDate) });
        }
    }
}

Проверка узлов верхнего уровня

Узлы верхнего уровня содержат:

  • Параметры действия
  • свойства контроллера;
  • параметры обработчика страниц;
  • свойства страничной модели.

Проверка привязанных к модели узлов верхнего уровня осуществляется наряду с проверкой свойств модели. В следующем примере из примера приложенияVerifyPhone метод использует RegularExpressionAttribute метод для проверки phone параметра действия:

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyPhone(
    [RegularExpression(@"^\d{3}-\d{3}-\d{4}$")] string phone)
{
    if (!ModelState.IsValid)
    {
        return Json($"Phone {phone} has an invalid format. Format: ###-###-####");
    }

    return Json(true);
}

Узлы верхнего уровня могут применять класс BindRequiredAttribute с атрибутами проверки. В следующем примере из примера приложения метод указывает, CheckAge что age параметр должен быть привязан из строки запроса при отправке формы:

[HttpPost]
public IActionResult CheckAge([BindRequired, FromQuery] int age)
{

На странице "Контрольный возраст" (CheckAge.cshtml) есть две формы. Первая форма отправляет значение Age, равное 99, в виде параметра строки запроса: https://localhost:5001/Users/CheckAge?Age=99.

Если из строки запроса отправлен параметр age в правильном формате, форма проходит проверку.

Вторая форма на странице "Check Age" (Проверка возраста) отправляет значение Age в теле запроса, и проверка завершается сбоем. Ошибка привязки связана с тем, что параметр age должен поступать из строки запроса.

Максимальное количество ошибок

При достижении максимального количества ошибок (по умолчанию 200) проверка прекращается. Это число можно изменить с помощью следующего кода в Program.cs:

builder.Services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.MaxModelValidationErrors = 50;
        options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
            _ => "The field is required.");
    });

builder.Services.AddSingleton
    <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();

Максимальная рекурсия

ValidationVisitor проходит через граф объектов в проверяемой модели. У глубоких моделей, содержащих бесконечную рекурсию, в ходе проверки может произойти переполнение стека. MvcOptions.MaxValidationDepth предоставляет способ остановить проверку рано, если рекурсия посетителя превышает настроенную глубину. Значение MvcOptions.MaxValidationDepth по умолчанию —32.

Автоматическое сокращение

Проверка автоматически сокращается (пропускается), если граф модели не требует проверки. К числу объектов, которые среда выполнения пропускает при проверке, относятся коллекции примитивов (такие как byte[], string[], Dictionary<string, string>) и сложные графы объектов, которые не имеют проверяющих элементов управления.

Проверка на стороне клиента

Проверка на стороне клиента не позволяет отправлять форму, пока ее данные не будут допустимыми. При нажатии кнопки "Отправить" выполняется код JavaScript, который либо отправляет форму, либо выводит сообщения об ошибках.

Проверка на стороне клиента позволяет избежать ненужного кругового захода на сервер при наличии ошибки ввода в форме. Следующие ссылки на скрипты и _Layout.cshtml _ValidationScriptsPartial.cshtml поддерживают проверку на стороне клиента:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/jquery.validate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.12/jquery.validate.unobtrusive.js"></script>

Скрипт проверки jQuery Unobtrusive Validation — это пользовательская интерфейсная библиотека Майкрософт, которая основана на популярном подключаемом модуле проверки jQuery. Без скрипта ненавязчивой проверки jQuery одну и ту же логику проверки приходилось бы реализовывать в двух местах: в атрибутах проверки для свойств модели на стороне сервера, а затем еще раз в скриптах на стороне клиента. Вместо этого вспомогательные функции тегов и вспомогательные функции HTML могут использовать атрибуты проверки и метаданные типов из свойств модели для обработки атрибутов data- HTML 5 в элементах форм, требующих проверки. JQuery Untrusive Validation анализирует data- атрибуты и передает логику в jQuery Validation, эффективно копируя логику проверки на стороне сервера клиенту. Ошибки проверки могут выводиться в клиенте с помощью вспомогательных функций тегов, как показано ниже.

<div class="form-group">
    <label asp-for="Movie.ReleaseDate" class="control-label"></label>
    <input asp-for="Movie.ReleaseDate" class="form-control" />
    <span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
</div>

Приведенные выше вспомогательные функции тегов отрисовывают следующий код HTML:

<div class="form-group">
    <label class="control-label" for="Movie_ReleaseDate">Release Date</label>
    <input class="form-control" type="date" data-val="true"
        data-val-required="The Release Date field is required."
        id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">
    <span class="text-danger field-validation-valid"
        data-valmsg-for="Movie.ReleaseDate" data-valmsg-replace="true"></span>
</div>

Обратите внимание на то, что атрибуты data- в выходных данных HTML соответствуют атрибутам проверки для свойства Movie.ReleaseDate. Атрибут data-val-required содержит сообщение об ошибке, которое выводится, если пользователь не заполнил поле даты выхода. jQuery Unobtrusive Validation передает это значение методу jQuery Validation required(), который затем отображает это сообщение в сопровождающем <элементе диапазона> .

Проверка типов данных основана на типе свойства .NET, если только это не переопределяется атрибутом [DataType] . Браузеры имеют свои сообщения об по умолчанию, но пакет ненавязчивой проверки jQuery может переопределять эти сообщения. [DataType] атрибуты и подклассы, такие как [EmailAddress] , позволяют указать сообщение об ошибке.

Ненавязчивая проверка

См. сведения о ненавязчивой проверке в этой проблеме GitHub.

Добавление проверки к динамическим формам

JQuery Unobtrusive Validation передает логику проверки и параметры jQuery Validation при первой загрузке страницы. Поэтому динамически создаваемые формы не подвергаются проверке автоматически. Чтобы включить проверку, необходимо указать, что скрипт ненавязчивой проверки jQuery должен анализировать динамическую форму сразу после ее создания. Например, приведенный ниже код показывает, как можно настроить проверку на стороне клиента для формы, добавленной посредством AJAX.

$.get({
    url: "https://url/that/returns/a/form",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add form. " + errorThrown);
    },
    success: function(newFormHTML) {
        var container = document.getElementById("form-container");
        container.insertAdjacentHTML("beforeend", newFormHTML);
        var forms = container.getElementsByTagName("form");
        var newForm = forms[forms.length - 1];
        $.validator.unobtrusive.parse(newForm);
    }
})

Метод $.validator.unobtrusive.parse() принимает селектор jQuery в качестве единственного аргумента. Этот метод предписывает скрипту ненавязчивой проверки jQuery анализировать атрибуты data- форм в этом селекторе. Затем значения этих атрибутов передаются в подключаемый модуль проверки jQuery.

Добавление проверки к динамическим элементам управления

Метод $.validator.unobtrusive.parse() обрабатывает всю форму, а не отдельные динамически создаваемые элементы управления, такие как <input> и <select/>. Для повторной обработки формы удалите данные проверки, которые были добавлены при анализе формы ранее, как показано в следующем примере:

$.get({
    url: "https://url/that/returns/a/control",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add control. " + errorThrown);
    },
    success: function(newInputHTML) {
        var form = document.getElementById("my-form");
        form.insertAdjacentHTML("beforeend", newInputHTML);
        $(form).removeData("validator")    // Added by jQuery Validation
               .removeData("unobtrusiveValidation");   // Added by jQuery Unobtrusive Validation
        $.validator.unobtrusive.parse(form);
    }
})

Настраиваемая проверка на стороне клиента

Настраиваемая проверка на стороне клиента выполняется путем data- создания HTML-атрибутов, работающих с пользовательским адаптером проверки jQuery. В следующем примере кода адаптера используются атрибуты [ClassicMovie] и [ClassicMovieWithClientValidator], которые были введены ранее в этой статье.

$.validator.addMethod('classicmovie', function (value, element, params) {
    var genre = $(params[0]).val(), year = params[1], date = new Date(value);

    // The Classic genre has a value of '0'.
    if (genre && genre.length > 0 && genre[0] === '0') {
        // The release date for a Classic is valid if it's no greater than the given year.
        return date.getUTCFullYear() <= year;
    }

    return true;
});

$.validator.unobtrusive.adapters.add('classicmovie', ['year'], function (options) {
    var element = $(options.form).find('select#Movie_Genre')[0];

    options.rules['classicmovie'] = [element, parseInt(options.params['year'])];
    options.messages['classicmovie'] = options.message;
});

Сведения о том, как записывать адаптеры, см. в документации по проверке jQuery.

Использование адаптера для заданного поля инициируется атрибутами data-, которые:

  • помечают поле как проходящее проверку (data-val="true");
  • указывают имя правила проверки и текст сообщения об ошибке (например, data-val-rulename="Error message.");
  • указывают любые дополнительные параметры, в которых нуждается проверяющий элемент управления (например, data-val-rulename-param1="value").

В следующем примере показаны data- атрибуты атрибута ClassicMovie примера приложения:

<input class="form-control" type="date"
    data-val="true"
    data-val-classicmovie="Classic movies must have a release year no later than 1960."
    data-val-classicmovie-year="1960"
    data-val-required="The Release Date field is required."
    id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">

Как отмечалось ранее, вспомогательные функции тегов и вспомогательные методы HTML используют сведения из атрибутов проверки для подготовки к отрисовке атрибутов data-. Существует два варианта для написания кода, который приводит к созданию настраиваемых атрибутов HTML data-.

  • Создайте класс, производный от AttributeAdapterBase<TAttribute>, и класс, реализующий IValidationAttributeAdapterProvider; зарегистрируйте атрибут и его адаптер в функции внедрения зависимостей. Этот метод следует единому принципу ответственности в коде проверки, связанном с сервером, и коде проверки, связанном с клиентом, в отдельных классах. Адаптер также имеет преимущество, так как он зарегистрирован в DI, другие службы в DI доступны для него при необходимости.
  • Реализуйте IClientModelValidator в вашем классе ValidationAttribute. Этот метод стоит использовать, если атрибут не выполняет проверку на стороне сервера и не нуждается в службах из функции внедрения зависимостей.

AttributeAdapter для проверки на стороне клиента

Этот метод отрисовки data- атрибутов в HTML используется атрибутом ClassicMovie в примере приложения. Чтобы добавить клиентскую проверку с помощью этого метода, сделайте следующее.

  1. Создайте класс адаптера атрибута для настраиваемого атрибута проверки. Создайте класс, производный от AttributeAdapterBase<TAttribute>. Создайте метод AddValidation, который добавляет атрибуты data- для выводимых данных, как показано в следующем примере.

    public class ClassicMovieAttributeAdapter : AttributeAdapterBase<ClassicMovieAttribute>
    {
        public ClassicMovieAttributeAdapter(
            ClassicMovieAttribute attribute, IStringLocalizer? stringLocalizer)
            : base(attribute, stringLocalizer)
        {
    
        }
    
        public override void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage(context));
    
            var year = Attribute.Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
    
        public override string GetErrorMessage(ModelValidationContextBase validationContext)
            => Attribute.GetErrorMessage();
    }
    
  2. Создайте класс поставщика адаптера, который реализует IValidationAttributeAdapterProvider. В методе GetAttributeAdapter передайте настраиваемый атрибут в конструктор адаптера, как показано в следующем примере.

    public class CustomValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider
    {
        private readonly IValidationAttributeAdapterProvider baseProvider =
            new ValidationAttributeAdapterProvider();
    
        public IAttributeAdapter? GetAttributeAdapter(
            ValidationAttribute attribute, IStringLocalizer? stringLocalizer)
        {
            if (attribute is ClassicMovieAttribute classicMovieAttribute)
            {
                return new ClassicMovieAttributeAdapter(classicMovieAttribute, stringLocalizer);
            }
    
            return baseProvider.GetAttributeAdapter(attribute, stringLocalizer);
        }
    }
    
  3. Зарегистрируйте поставщик адаптера для внедрения зависимостей в Program.cs.

    builder.Services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    builder.Services.AddSingleton
        <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();
    

IClientModelValidator для проверки на стороне клиента

Этот метод отрисовки data- атрибутов в HTML используется атрибутом ClassicMovieWithClientValidator в примере приложения. Чтобы добавить клиентскую проверку с помощью этого метода, сделайте следующее.

  • В настраиваемом атрибуте проверки реализуйте интерфейс IClientModelValidator и создайте метод AddValidation. В метод AddValidation добавьте атрибуты data- для проверки, как показано в следующем примере.

    public class ClassicMovieWithClientValidatorAttribute :
        ValidationAttribute, IClientModelValidator
    {
        public ClassicMovieWithClientValidatorAttribute(int year)
            => Year = year;
    
        public int Year { get; }
    
        public void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage());
    
            var year = Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
    
        public string GetErrorMessage() =>
            $"Classic movies must have a release year no later than {Year}.";
    
        protected override ValidationResult? IsValid(
            object? value, ValidationContext validationContext)
        {
            var movie = (Movie)validationContext.ObjectInstance;
            var releaseYear = ((DateTime)value!).Year;
    
            if (movie.Genre == Genre.Classic && releaseYear > Year)
            {
                return new ValidationResult(GetErrorMessage());
            }
    
            return ValidationResult.Success;
        }
    
        private static bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
        {
            if (attributes.ContainsKey(key))
            {
                return false;
            }
    
            attributes.Add(key, value);
            return true;
        }
    }
    

Отключение проверки на стороне клиента

Следующий код отключает проверку клиента в Razor Pages:

builder.Services.AddRazorPages()
    .AddViewOptions(options =>
    {
        options.HtmlHelperOptions.ClientValidationEnabled = false;
    });

Другие параметры отключения проверки на стороне клиента:

  • Закомментируйте ссылку _ValidationScriptsPartial на все .cshtml файлы.
  • Удалите содержимое файла Pages\Shared_ValidationScriptsPartial.cshtml .

Приведенный выше подход не предотвращает проверку на стороне клиента библиотеки классов ASP.NET Core IdentityRazor . Дополнительные сведения см. в разделе Шаблоны Identity в проектах ASP.NET Core.

Дополнительные ресурсы

В этой статье объясняется, как проверить входные данные пользователей в приложении ASP.NET Core MVC или Razor Pages.

Просмотреть или скачать пример кода (описание скачивания).

Состояние модели

Состояние модели представляет ошибки, создаваемые двумя подсистемами: привязкой модели и проверкой модели. Ошибки привязки модели обычно являются ошибками преобразования данных. Например, в целочисленном поле указывается "x". Проверка модели происходит после ее привязки. В процессе сообщается об ошибках несоответствия данных бизнес-правилам. Например, в поле, которое ожидает оценку от 1 до 5, указывается 0.

Привязка модели и проверка модели выполняются перед выполнением действия контроллера или Razor метода обработчика Pages. Веб-приложение отвечает за проверку ModelState.IsValid и реагирует соответствующим образом. Веб-приложения обычно повторно отображают страницы с сообщением об ошибке.

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Контроллеры веб-API не должны проверять ModelState.IsValid наличие атрибута [ApiController] . В этом случае автоматически возвращается ответ HTTP 400, содержащий сведения об ошибке, если состояние модели недопустимо. Дополнительные сведения см. в разделе Автоматические отклики HTTP 400.

Перезапуск проверки

Проверка выполняется автоматически, однако может потребоваться повторить ее вручную. Например, можно вычислить значение свойства и повторно выполнить проверку после установки свойства в вычисляемое значение. Чтобы повторно выполнить проверку, вызовите ModelStateDictionary.ClearValidationState очистку проверки, относяшейся к модели, за которой следует TryValidateModel:

public async Task<IActionResult> OnPostTryValidateAsync()
{
    var modifiedReleaseDate = DateTime.Now.Date;
    Movie.ReleaseDate = modifiedReleaseDate;

    ModelState.ClearValidationState(nameof(Movie));
    if (!TryValidateModel(Movie, nameof(Movie)))
    {
        return Page();
    }

    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Атрибуты проверки

Атрибуты проверки позволяют задать правила проверки для свойств модели. В следующем примере из примера приложения показан класс модели, который помечается с помощью атрибутов проверки. Атрибут [ClassicMovie] — это настраиваемый атрибут проверки, а другие — встроенные. Не показан [ClassicMovieWithClientValidator], который демонстрирует альтернативный способ реализации настраиваемого атрибута.

public class Movie
{
    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; }

    [ClassicMovie(1960)]
    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; }

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }
}

Встроенные атрибуты

Ниже приведены некоторые из встроенных атрибутов проверки.

  • [ValidateNever]: указывает, что свойство или параметр следует исключить из проверки.
  • [Кредитная карта]: проверяет, имеет ли свойство формат кредитной карты. Требуется дополнительный метод проверки jQuery.
  • [Сравнение]: проверяет, соответствуют ли два свойства в модели.
  • [EmailAddress]: проверяет, имеет ли свойство формат электронной почты.
  • [Телефон]: проверяет, имеет ли свойство формат номера телефона.
  • [Диапазон]: проверяет, соответствует ли значение свойства указанному диапазону.
  • [RegularExpression]: проверяет, соответствует ли значение свойства указанному регулярному выражению.
  • [Обязательно]: проверяет, не является ли поле пустым. Дополнительные сведения о поведении этого атрибута см. в разделе Атрибут [Required].
  • [StringLength]: проверяет, что строковое значение свойства не превышает указанное ограничение длины.
  • [URL-адрес]: проверяет, имеет ли свойство формат URL-адреса.
  • [Remote]: проверяет входные данные на клиенте путем вызова метода действия на сервере. Дополнительные сведения о поведении этого атрибута см. в разделе Атрибут [Remote].

Полный список атрибутов проверки можно найти в System.ComponentModel.DataAnnotations пространстве имен.

Сообщения об ошибках

Атрибуты проверки позволяют указать сообщение об ошибке, которое будет отображаться, если входные данные недопустимы. Например:

[StringLength(8, ErrorMessage = "Name length can't be more than 8.")]

На внутреннем уровне атрибуты вызывают String.Format с заполнителем для имени поля и иногда дополнительным заполнителями. Например:

[StringLength(8, ErrorMessage = "{0} length must be between {2} and {1}.", MinimumLength = 6)]

При применении к свойству Name сообщение об ошибке, созданное в приведенном выше коде, имело бы вид Name length must be between 6 and 8 (Длина имени должна быть от 6 до 8).

Чтобы узнать, какие параметры передаются в String.Format для сообщения об ошибке определенного атрибута, см. раздел Исходный код DataAnnotations.

Непустимые ссылочные типы и атрибут [Обязательный]

Система проверки обрабатывает параметры, не допускающие значения NULL, или привязанные свойства, как если бы они имели [Required(AllowEmptyStrings = true)] атрибут. Включив Nullable контексты, MVC неявно начинает проверку ненулевого свойства или параметров, как если бы они были атрибутом[Required(AllowEmptyStrings = true)]. Рассмотрим следующий код:

public class Person
{
    public string Name { get; set; }
}

Если приложение было создано с <Nullable>enable</Nullable>помощью, отсутствующее значение Name в формате JSON или записи формы приводит к ошибке проверки. Используйте тип ссылки, допускающий значение NULL, чтобы разрешить указывать значения NULL или отсутствующие значения для Name свойства:

public class Person
{
    public string? Name { get; set; }
}

Это поведение можно отключить с помощью параметра SuppressImplicitRequiredAttributeForNonNullableReferenceTypes в Startup.ConfigureServices:

services.AddControllers(options => options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true);

Проверка [Required] на сервере

На сервере обязательное значение считается отсутствующим, если свойство имеет значение NULL. Поле, не допускающее значения NULL, всегда является допустимым, и сообщение об ошибке атрибута [Required] никогда не выводится.

Тем не менее привязка модели для ненулевого свойства может завершиться ошибкой, приводящей к сообщению об ошибке, например The value '' is invalid. Чтобы задать настраиваемое сообщение об ошибке во время проверки не допускающих значения NULL типов на стороне сервера, у вас есть следующие варианты.

  • Сделать поле допускающим значение NULL (например, decimal? вместо decimal). Типы значений,> допускающие<значение NULL, рассматриваются как стандартные типы, допускающие значение NULL.

  • Указать сообщение об ошибке по умолчанию для использования в привязке модели, как показано в следующем примере.

    services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    services.AddSingleton<IValidationAttributeAdapterProvider,
        CustomValidationAttributeAdapterProvider>();
    

    Дополнительные сведения об ошибках привязки модели, у которых можно задать сообщение по умолчанию, см. в разделе DefaultModelBindingMessageProvider.

Проверка [Required] на клиенте

Типы и строки, не допускающие значение NULL, обрабатываются на клиенте не так, как на сервере. На клиенте:

  • Значение считается присутствующим только в том случае, если для него вводятся данные. Таким образом, проверка на стороне клиента обрабатывает типы, не допускающие значение NULL, так же, как обнуляемые типы.
  • Пробелы в поле строки считаются допустимыми входными данными при проверке методом jQuery required. Проверка на стороне сервера считает, что обязательное строковое поле недопустимо, если введены только пробелы.

Как отмечалось ранее, не допускающие значение NULL типы рассматриваются как имеющие атрибут [Required(AllowEmptyStrings = true)]. Это означает, что вы получаете проверку на стороне клиента, даже если не применять атрибут [Required(AllowEmptyStrings = true)]. Но если вы не используете атрибут, вы получаете сообщение об ошибке по умолчанию. Чтобы задать настраиваемое сообщение об ошибке, используйте атрибут.

Атрибут [Remote]

Атрибут [Remote] реализует проверку на стороне клиента, требующую вызова метода на сервере для определения допустимости входных данных поля. Например, приложению может потребоваться проверить, занято ли имя пользователя.

Для реализации удаленной проверки сделайте следующее.

  1. Создайте для вызова из JavaScript метод действия. Удаленный метод проверки jQuery ожидает ответ JSON:

    • true означает, что входные данные допустимы.
    • false, undefined или null означают, что входные данные недопустимы. Вывод стандартного сообщения об ошибке
    • Все прочие значения означают, что входные данные недопустимы. Вывод строки как настраиваемого сообщения об ошибке.

    Ниже приведен пример метода действия, который возвращает настраиваемое сообщение об ошибке.

    [AcceptVerbs("GET", "POST")]
    public IActionResult VerifyEmail(string email)
    {
        if (!_userService.VerifyEmail(email))
        {
            return Json($"Email {email} is already in use.");
        }
    
        return Json(true);
    }
    
  2. В классе модели пометьте свойство атрибутом [Remote], указывающим метод действия проверки, как показано в следующем примере.

    [Remote(action: "VerifyEmail", controller: "Users")]
    public string Email { get; set; }
    

Дополнительные поля

Свойство AdditionalFields атрибута [Remote] позволяет проверять сочетания полей с данными на сервере. Например, если бы в модели User были свойства FirstName и LastName, могла бы возникнуть необходимость проверить, нет ли уже пользователя с такой парой имен. В следующем примере показано использование AdditionalFields.

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(LastName))]
[Display(Name = "First Name")]
public string FirstName { get; set; }

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName))]
[Display(Name = "Last Name")]
public string LastName { get; set; }

AdditionalFields можно явно присвоить строкам "FirstName" и "LastName", но использование оператора nameof упрощает дальнейший рефакторинг. Методу действия для этой проверки необходимо принимать аргументы firstName и lastName

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyName(string firstName, string lastName)
{
    if (!_userService.VerifyName(firstName, lastName))
    {
        return Json($"A user named {firstName} {lastName} already exists.");
    }

    return Json(true);
}

Когда пользователь вводит имя или фамилию, JavaScript выполняет удаленный вызов, чтобы увидеть, будет ли эта пара принята.

Чтобы проверить несколько дополнительных полей, их следует указывать в виде списка с разделителями-запятыми. Например, чтобы добавить в модель свойство MiddleName, задайте атрибут [Remote], как показано в следующем примере.

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName) + "," + nameof(LastName))]
public string MiddleName { get; set; }

AdditionalFields, как и все аргументы атрибутов, должен представлять собой константное выражение. Поэтому не следует использовать интерполированную строку или вызов Joinдля инициализации AdditionalFields.

Альтернативы для встроенных атрибутов

Если вам нужна проверка, которую не предоставляют встроенные атрибуты, вы можете следующее.

Настраиваемые атрибуты

Для сценариев, где не годятся встроенные атрибуты проверки, можно создать настраиваемые атрибуты. Создайте класс, наследуемый от ValidationAttribute, и переопределите метод IsValid.

Метод IsValid принимает объект с именем value, который является входными данными для проверки. Перегрузка также принимает объект ValidationContext, который предоставляет дополнительные сведения, такие как экземпляр модели, созданный с помощью привязки модели.

В следующем примере проверяется, что дата выпуска фильмов в классическом жанре задана не позднее указанного года. Атрибут [ClassicMovie]:

  • выполняется только на сервере.
  • Для классических фильмов проверяет дату выпуска:
public class ClassicMovieAttribute : ValidationAttribute
{
    public ClassicMovieAttribute(int year)
    {
        Year = year;
    }

    public int Year { get; }

    public string GetErrorMessage() =>
        $"Classic movies must have a release year no later than {Year}.";

    protected override ValidationResult IsValid(object value,
        ValidationContext validationContext)
    {
        var movie = (Movie)validationContext.ObjectInstance;
        var releaseYear = ((DateTime)value).Year;

        if (movie.Genre == Genre.Classic && releaseYear > Year)
        {
            return new ValidationResult(GetErrorMessage());
        }

        return ValidationResult.Success;
    }
}

Приведенная выше переменная movie представляет объект Movie, который содержит данные из переданной формы. Если проверка завершается неудачно, возвращается ValidationResult с сообщением об ошибке.

IValidatableObject

Предыдущий пример работает только с типами Movie. Другой вариант для проверки на уровне класса — реализация IValidatableObject в классе модели, как показано в следующем примере.

public class ValidatableMovie : IValidatableObject
{
    private const int _classicYear = 1960;

    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; }

    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; }

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Genre == Genre.Classic && ReleaseDate.Year > _classicYear)
        {
            yield return new ValidationResult(
                $"Classic movies must have a release year no later than {_classicYear}.",
                new[] { nameof(ReleaseDate) });
        }
    }
}

Проверка узлов верхнего уровня

Узлы верхнего уровня содержат:

  • Параметры действия
  • свойства контроллера;
  • параметры обработчика страниц;
  • свойства страничной модели.

Проверка привязанных к модели узлов верхнего уровня осуществляется наряду с проверкой свойств модели. В следующем примере из примера приложенияVerifyPhone метод использует RegularExpressionAttribute метод для проверки phone параметра действия:

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyPhone(
    [RegularExpression(@"^\d{3}-\d{3}-\d{4}$")] string phone)
{
    if (!ModelState.IsValid)
    {
        return Json($"Phone {phone} has an invalid format. Format: ###-###-####");
    }

    return Json(true);
}

Узлы верхнего уровня могут применять класс BindRequiredAttribute с атрибутами проверки. В следующем примере из примера приложения метод указывает, CheckAge что age параметр должен быть привязан из строки запроса при отправке формы:

[HttpPost]
public IActionResult CheckAge([BindRequired, FromQuery] int age)
{

На странице "Контрольный возраст" (CheckAge.cshtml) есть две формы. Первая форма отправляет значение Age, равное 99, в виде параметра строки запроса: https://localhost:5001/Users/CheckAge?Age=99.

Если из строки запроса отправлен параметр age в правильном формате, форма проходит проверку.

Вторая форма на странице "Check Age" (Проверка возраста) отправляет значение Age в теле запроса, и проверка завершается сбоем. Ошибка привязки связана с тем, что параметр age должен поступать из строки запроса.

Максимальное количество ошибок

При достижении максимального количества ошибок (по умолчанию 200) проверка прекращается. Это число можно изменить с помощью следующего кода в Startup.ConfigureServices:

services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.MaxModelValidationErrors = 50;
        options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
            _ => "The field is required.");
    });

services.AddSingleton<IValidationAttributeAdapterProvider,
    CustomValidationAttributeAdapterProvider>();

Максимальная рекурсия

ValidationVisitor проходит через граф объектов в проверяемой модели. У глубоких моделей, содержащих бесконечную рекурсию, в ходе проверки может произойти переполнение стека. MvcOptions.MaxValidationDepth предоставляет способ остановить проверку рано, если рекурсия посетителя превышает настроенную глубину. Значение MvcOptions.MaxValidationDepth по умолчанию —32.

Автоматическое сокращение

Проверка автоматически сокращается (пропускается), если граф модели не требует проверки. К числу объектов, которые среда выполнения пропускает при проверке, относятся коллекции примитивов (такие как byte[], string[], Dictionary<string, string>) и сложные графы объектов, которые не имеют проверяющих элементов управления.

Проверка на стороне клиента

Проверка на стороне клиента не позволяет отправлять форму, пока ее данные не будут допустимыми. При нажатии кнопки "Отправить" выполняется код JavaScript, который либо отправляет форму, либо выводит сообщения об ошибках.

Проверка на стороне клиента позволяет избежать ненужного кругового захода на сервер при наличии ошибки ввода в форме. Следующие ссылки на скрипты и _Layout.cshtml _ValidationScriptsPartial.cshtml поддерживают проверку на стороне клиента:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.1/jquery.validate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.11/jquery.validate.unobtrusive.js"></script>

Скрипт проверки jQuery Unobtrusive Validation — это пользовательская интерфейсная библиотека Майкрософт, которая основана на популярном подключаемом модуле проверки jQuery. Без скрипта ненавязчивой проверки jQuery одну и ту же логику проверки приходилось бы реализовывать в двух местах: в атрибутах проверки для свойств модели на стороне сервера, а затем еще раз в скриптах на стороне клиента. Вместо этого вспомогательные функции тегов и вспомогательные функции HTML могут использовать атрибуты проверки и метаданные типов из свойств модели для обработки атрибутов data- HTML 5 в элементах форм, требующих проверки. JQuery Untrusive Validation анализирует data- атрибуты и передает логику в jQuery Validation, эффективно копируя логику проверки на стороне сервера клиенту. Ошибки проверки могут выводиться в клиенте с помощью вспомогательных функций тегов, как показано ниже.

<div class="form-group">
    <label asp-for="Movie.ReleaseDate" class="control-label"></label>
    <input asp-for="Movie.ReleaseDate" class="form-control" />
    <span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
</div>

Приведенные выше вспомогательные функции тегов отрисовывают следующий код HTML:

<div class="form-group">
    <label class="control-label" for="Movie_ReleaseDate">Release Date</label>
    <input class="form-control" type="date" data-val="true"
        data-val-required="The Release Date field is required."
        id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">
    <span class="text-danger field-validation-valid"
        data-valmsg-for="Movie.ReleaseDate" data-valmsg-replace="true"></span>
</div>

Обратите внимание на то, что атрибуты data- в выходных данных HTML соответствуют атрибутам проверки для свойства Movie.ReleaseDate. Атрибут data-val-required содержит сообщение об ошибке, которое выводится, если пользователь не заполнил поле даты выхода. jQuery Unobtrusive Validation передает это значение методу jQuery Validation required(), который затем отображает это сообщение в сопровождающем <элементе диапазона> .

Проверка типов данных основана на типе свойства .NET, если только это не переопределяется атрибутом [DataType] . Браузеры имеют свои сообщения об по умолчанию, но пакет ненавязчивой проверки jQuery может переопределять эти сообщения. [DataType] атрибуты и подклассы, такие как [EmailAddress] , позволяют указать сообщение об ошибке.

Ненавязчивая проверка

См. сведения о ненавязчивой проверке в этой проблеме GitHub.

Добавление проверки к динамическим формам

JQuery Unobtrusive Validation передает логику проверки и параметры jQuery Validation при первой загрузке страницы. Поэтому динамически создаваемые формы не подвергаются проверке автоматически. Чтобы включить проверку, необходимо указать, что скрипт ненавязчивой проверки jQuery должен анализировать динамическую форму сразу после ее создания. Например, приведенный ниже код показывает, как можно настроить проверку на стороне клиента для формы, добавленной посредством AJAX.

$.get({
    url: "https://url/that/returns/a/form",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add form. " + errorThrown);
    },
    success: function(newFormHTML) {
        var container = document.getElementById("form-container");
        container.insertAdjacentHTML("beforeend", newFormHTML);
        var forms = container.getElementsByTagName("form");
        var newForm = forms[forms.length - 1];
        $.validator.unobtrusive.parse(newForm);
    }
})

Метод $.validator.unobtrusive.parse() принимает селектор jQuery в качестве единственного аргумента. Этот метод предписывает скрипту ненавязчивой проверки jQuery анализировать атрибуты data- форм в этом селекторе. Затем значения этих атрибутов передаются в подключаемый модуль проверки jQuery.

Добавление проверки к динамическим элементам управления

Метод $.validator.unobtrusive.parse() обрабатывает всю форму, а не отдельные динамически создаваемые элементы управления, такие как <input> и <select/>. Для повторной обработки формы удалите данные проверки, которые были добавлены при анализе формы ранее, как показано в следующем примере:

$.get({
    url: "https://url/that/returns/a/control",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add control. " + errorThrown);
    },
    success: function(newInputHTML) {
        var form = document.getElementById("my-form");
        form.insertAdjacentHTML("beforeend", newInputHTML);
        $(form).removeData("validator")    // Added by jQuery Validation
               .removeData("unobtrusiveValidation");   // Added by jQuery Unobtrusive Validation
        $.validator.unobtrusive.parse(form);
    }
})

Настраиваемая проверка на стороне клиента

Настраиваемая проверка на стороне клиента выполняется путем data- создания HTML-атрибутов, работающих с пользовательским адаптером проверки jQuery. В следующем примере кода адаптера используются атрибуты [ClassicMovie] и [ClassicMovieWithClientValidator], которые были введены ранее в этой статье.

$.validator.addMethod('classicmovie', function (value, element, params) {
    var genre = $(params[0]).val(), year = params[1], date = new Date(value);

    // The Classic genre has a value of '0'.
    if (genre && genre.length > 0 && genre[0] === '0') {
        // The release date for a Classic is valid if it's no greater than the given year.
        return date.getUTCFullYear() <= year;
    }

    return true;
});

$.validator.unobtrusive.adapters.add('classicmovie', ['year'], function (options) {
    var element = $(options.form).find('select#Movie_Genre')[0];

    options.rules['classicmovie'] = [element, parseInt(options.params['year'])];
    options.messages['classicmovie'] = options.message;
});

Сведения о том, как записывать адаптеры, см. в документации по проверке jQuery.

Использование адаптера для заданного поля инициируется атрибутами data-, которые:

  • помечают поле как проходящее проверку (data-val="true");
  • указывают имя правила проверки и текст сообщения об ошибке (например, data-val-rulename="Error message.");
  • указывают любые дополнительные параметры, в которых нуждается проверяющий элемент управления (например, data-val-rulename-param1="value").

В следующем примере показаны data- атрибуты атрибута ClassicMovie примера приложения:

<input class="form-control" type="date"
    data-val="true"
    data-val-classicmovie="Classic movies must have a release year no later than 1960."
    data-val-classicmovie-year="1960"
    data-val-required="The Release Date field is required."
    id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">

Как отмечалось ранее, вспомогательные функции тегов и вспомогательные методы HTML используют сведения из атрибутов проверки для подготовки к отрисовке атрибутов data-. Существует два варианта для написания кода, который приводит к созданию настраиваемых атрибутов HTML data-.

  • Создайте класс, производный от AttributeAdapterBase<TAttribute>, и класс, реализующий IValidationAttributeAdapterProvider; зарегистрируйте атрибут и его адаптер в функции внедрения зависимостей. Этот метод следует единому принципу ответственности в коде проверки, связанном с сервером, и коде проверки, связанном с клиентом, в отдельных классах. Адаптер также имеет преимущество, так как он зарегистрирован в DI, другие службы в DI доступны для него при необходимости.
  • Реализуйте IClientModelValidator в вашем классе ValidationAttribute. Этот метод стоит использовать, если атрибут не выполняет проверку на стороне сервера и не нуждается в службах из функции внедрения зависимостей.

AttributeAdapter для проверки на стороне клиента

Этот метод отрисовки data- атрибутов в HTML используется атрибутом ClassicMovie в примере приложения. Чтобы добавить клиентскую проверку с помощью этого метода, сделайте следующее.

  1. Создайте класс адаптера атрибута для настраиваемого атрибута проверки. Создайте класс, производный от AttributeAdapterBase<TAttribute>. Создайте метод AddValidation, который добавляет атрибуты data- для выводимых данных, как показано в следующем примере.

    public class ClassicMovieAttributeAdapter : AttributeAdapterBase<ClassicMovieAttribute>
    {
        public ClassicMovieAttributeAdapter(ClassicMovieAttribute attribute,
            IStringLocalizer stringLocalizer)
            : base(attribute, stringLocalizer)
        {
    
        }
    
        public override void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage(context));
    
            var year = Attribute.Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
    
        public override string GetErrorMessage(ModelValidationContextBase validationContext) =>
            Attribute.GetErrorMessage();
    }
    
  2. Создайте класс поставщика адаптера, который реализует IValidationAttributeAdapterProvider. В методе GetAttributeAdapter передайте настраиваемый атрибут в конструктор адаптера, как показано в следующем примере.

    public class CustomValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider
    {
        private readonly IValidationAttributeAdapterProvider baseProvider =
            new ValidationAttributeAdapterProvider();
    
        public IAttributeAdapter GetAttributeAdapter(ValidationAttribute attribute,
            IStringLocalizer stringLocalizer)
        {
            if (attribute is ClassicMovieAttribute classicMovieAttribute)
            {
                return new ClassicMovieAttributeAdapter(classicMovieAttribute, stringLocalizer);
            }
    
            return baseProvider.GetAttributeAdapter(attribute, stringLocalizer);
        }
    }
    
  3. Зарегистрируйте поставщик адаптера для внедрения зависимостей в Startup.ConfigureServices.

    services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    services.AddSingleton<IValidationAttributeAdapterProvider,
        CustomValidationAttributeAdapterProvider>();
    

IClientModelValidator для проверки на стороне клиента

Этот метод отрисовки data- атрибутов в HTML используется атрибутом ClassicMovieWithClientValidator в примере приложения. Чтобы добавить клиентскую проверку с помощью этого метода, сделайте следующее.

  • В настраиваемом атрибуте проверки реализуйте интерфейс IClientModelValidator и создайте метод AddValidation. В метод AddValidation добавьте атрибуты data- для проверки, как показано в следующем примере.

    public class ClassicMovieWithClientValidatorAttribute :
        ValidationAttribute, IClientModelValidator
    {
        public ClassicMovieWithClientValidatorAttribute(int year)
        {
            Year = year;
        }
    
        public int Year { get; }
    
        public void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage());
    
            var year = Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
    
        public string GetErrorMessage() =>
            $"Classic movies must have a release year no later than {Year}.";
    
        protected override ValidationResult IsValid(object value,
            ValidationContext validationContext)
        {
            var movie = (Movie)validationContext.ObjectInstance;
            var releaseYear = ((DateTime)value).Year;
    
            if (movie.Genre == Genre.Classic && releaseYear > Year)
            {
                return new ValidationResult(GetErrorMessage());
            }
    
            return ValidationResult.Success;
        }
    
        private bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
        {
            if (attributes.ContainsKey(key))
            {
                return false;
            }
    
            attributes.Add(key, value);
            return true;
        }
    }
    

Отключение проверки на стороне клиента

Следующий код отключает проверку клиента в Razor Pages:

services.AddRazorPages()
    .AddViewOptions(options =>
    {
        options.HtmlHelperOptions.ClientValidationEnabled = false;
    });

Другие параметры отключения проверки на стороне клиента:

  • Закомментируйте ссылку _ValidationScriptsPartial на все .cshtml файлы.
  • Удалите содержимое файла Pages\Shared_ValidationScriptsPartial.cshtml .

Приведенный выше подход не предотвращает проверку на стороне клиента библиотеки классов ASP.NET Core IdentityRazor . Дополнительные сведения см. в разделе Шаблоны Identity в проектах ASP.NET Core.

Дополнительные ресурсы