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

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

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

Проверка в .NET 10

В .NET 10 унифицированные API проверки были перемещены в пакет NuGet Microsoft.Extensions.Validation. Это изменение делает API проверки доступными за пределами сценариев ASP.NET Core HTTP.

Чтобы использовать Microsoft.Extensions.Validation API, выполните следующие действия.

  • Добавьте следующую ссылку на пакет:

    <PackageReference Include="Microsoft.Extensions.Validation" Version="10.0.0" />
    

    Функция остается той же, но теперь требует явной ссылки на пакет.

  • Регистрация служб валидации с помощью внедрения зависимостей:

    builder.Services.AddValidation();
    

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

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

Привязка модели и проверка модели выполняются перед выполнением действия контроллера или Razor метода обработчика Pages. Веб-приложение отвечает за проверку ModelState.IsValid и реагирует соответствующим образом. Веб-приложения обычно повторно отображают страницу с сообщением об ошибке, как показано в следующем примере для "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; }
}

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

Чтобы ознакомиться с полным списком атрибутов проверки, см. пространство имен 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)] атрибут. Включив контексты, MVC неявно начинает проверку свойств или параметров, которые не могут быть нулевыми, как если бы они были обозначены атрибутом [Required(AllowEmptyStrings = true)]. Рассмотрим следующий код:

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

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

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

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

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

Проверка [Обязательный] на стороне клиента

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

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

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

Атрибут [Remote]

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

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

  1. Создайте функцию, которую будет вызывать JavaScript. Метод remote в jQuery Validation ожидает ответ в формате 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 Unobtrusive 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(), который затем отображает это сообщение в сопровождающем элементе <span>.

Проверка типов данных основана на типе свойства .NET, если только это не переопределяется атрибутом [DataType] . Браузеры имеют свои сообщения об ошибках по умолчанию, но пакет валидации jQuery Unobtrusive Validation может заменять эти сообщения. [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 Validation.

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

Метод $.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, другие службы в DI доступны для него при необходимости.
  • Реализуйте IClientModelValidator в вашем классе ValidationAttribute. Этот метод может быть подходящим, если атрибут не выполняет проверку на стороне сервера и не нуждается в услугах внедрения зависимостей (DI).

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. Зарегистрируйте поставщика адаптера для DI в 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-ответы с деталями проблемы, когда вызывается , за исключением случаев, когда заголовок HTTP-запроса не содержит ни одного из типов содержимого, поддерживаемых зарегистрированным (по умолчанию: ):

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

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

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

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

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

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

Привязка модели и проверка модели выполняются перед выполнением действия контроллера или Razor метода обработчика Pages. Веб-приложение отвечает за проверку ModelState.IsValid и реагирует соответствующим образом. Веб-приложения обычно повторно отображают страницу с сообщением об ошибке, как показано в следующем примере для "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]: указывает, что свойство или параметр следует исключить из проверки.
  • [CreditCard]: проверяет, имеет ли свойство формат кредитной карты. Требуется дополнительный метод проверки 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 или отправке формы приводит к ошибке валидации. Используйте nullable тип ссылки для указания NULL или отсутствующих значений для свойства Name.

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

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

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

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

Свойства, не допускающие значение 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). Типы значений Nullable<T> рассматриваются как стандартные типы, допускающие NULL.

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

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

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

Проверка обязательна на клиенте

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

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

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

Атрибут [Remote]

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

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

  1. Создайте метод действия, который будет вызываться из JavaScript. Метод remote в jQuery Validation ожидает ответ в форме 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 Unobtrusive 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(), который затем отображает это сообщение в сопровождающем элементе <span>.

Проверка типов данных основана на типе свойства .NET, если только это не переопределяется атрибутом [DataType] . Браузеры имеют свои сообщения об ошибках по умолчанию, но пакет валидации jQuery Unobtrusive Validation может заменять эти сообщения. [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 Validation.

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

Метод $.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, другие службы в DI доступны для него при необходимости.
  • Реализуйте IClientModelValidator в вашем классе ValidationAttribute. Этот метод может быть подходящим, если атрибут не выполняет проверку на стороне сервера и не нуждается в услугах внедрения зависимостей (DI).

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. Зарегистрируйте поставщика адаптера для DI в 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]: указывает, что свойство или параметр следует исключить из проверки.
  • [CreditCard]: проверяет, имеет ли свойство формат кредитной карты. Требуется дополнительный метод проверки 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)] атрибут. Включив контексты, MVC неявно начинает проверку свойств или параметров, которые не могут быть нулевыми, как если бы они были обозначены атрибутом [Required(AllowEmptyStrings = true)]. Рассмотрим следующий код:

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

Если приложение было создано с помощью <Nullable>enable</Nullable>, то отсутствие значения для Name в JSON или отправке формы приводит к ошибке валидации. Используйте nullable тип ссылки для указания 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). Типы значений Nullable<T> рассматриваются как стандартные типы, допускающие значение NULL.

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

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

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

Проверка [Обязательный] на стороне клиента

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

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

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

Атрибут [Remote]

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

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

  1. Создайте функцию, которую будет вызывать JavaScript. Метод remote в jQuery Validation ожидает ответ в формате 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 Unobtrusive 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(), который затем отображает это сообщение в сопровождающем элементе <span>.

Проверка типов данных основана на типе свойства .NET, если только это не переопределяется атрибутом [DataType] . Браузеры имеют свои сообщения об ошибках по умолчанию, но пакет валидации jQuery Unobtrusive Validation может заменять эти сообщения. [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 Validation.

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

Метод $.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, другие службы в DI доступны для него при необходимости.
  • Реализуйте IClientModelValidator в вашем классе ValidationAttribute. Этот метод может быть подходящим, если атрибут не выполняет проверку на стороне сервера и не нуждается в услугах внедрения зависимостей (DI).

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. Зарегистрируйте поставщика адаптера для DI в 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.

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