Проверка в ASP.NET Core

Microsoft.Extensions.Validation поддерживает сложную валидацию модели в проектах Blazor и Minimal API.

Хотя API в Microsoft.Extensions.Validation пакете NuGet можно использовать в сценариях вне ASP.NET Core, эта статья посвящена ASP.NET Core. API не поддерживается для MVC или Razor Pages. Рекомендации по проверке, применимые к MVC и Razor Pages, см. в разделе "Проверка модели" в ASP.NET Core MVC.

Чтобы включить проверку, вызовите AddValidation для WebApplicationBuilder.Services в файле Program приложения:

builder.Services.AddValidation();

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

Проверка использует генератор источника, который обнаруживает только допустимые типы в сборке, где AddValidation вызывается. Если конечные точки Minimal API определены в сборке, на которую есть ссылка, а не в сборке, где вызывается AddValidation, зарегистрируйте валидацию, как показано в разделе Регистрация валидации в приложениях с несколькими сборками.

Регистрация проверки в приложениях с несколькими сборками

Чтобы проверить типы из отдельных сборок, выполните следующие действия.

  • Если сборка является обычной библиотекой классов (она не основана на SDK Microsoft.NET.Sdk.Web или Microsoft.NET.Sdk.Razor), добавьте в проект ссылку на пакет NuGet Microsoft.Extensions.Validation.
  • Создайте в каждой внешней сборке метод расширения, который вызывает AddValidation.
  • Вызовите каждый из этих методов расширения в главном приложении.

Минимальный пример API

Если типы обработчиков конечных точек определяются для конечных точек в отдельной сборке API, но AddValidation вызываются только из сборки ведущего приложения, проверка не выполняется: недопустимые запросы обрабатываются и возвращают 200 - OK ответ вместо ожидаемого 400 - Bad Request ответа, даже если AddValidation зарегистрированы и типы запросов используют атрибуты проверки.

Создайте метод расширения коллекции служб в сборке, которая определяет минимальные конечные точки API и вызывает его из ведущего приложения.

ServiceCollectionExtensions.cs в сборке, определяющей конечные точки, в которой используется пространство имён из примера MinimalApisAssembly.Extensions:

namespace MinimalApisAssembly.Extensions;

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddApiValidation(
        this IServiceCollection services)
    {
        return services.AddValidation();
    }
}

В файле ведущего приложения Program вызовите метод расширения вместо вызова AddValidation напрямую:

using MinimalApisAssembly.Extensions;

...

builder.Services.AddApiValidation();

...

var app = builder.Build();

app.MapApi();

В предыдущем примере MapApi — это метод расширения, определённый в сборке endpoints и сопоставляющий конечные точки Minimal API. Определите его вместе с AddApiValidation тем, чтобы сопоставления конечных точек и проверка регистрировались из одной сборки.

Blazor Web App пример

Если типы моделей форм определены в отдельной библиотеке или в проекте Blazor Web App компонента .Client, но AddValidation вызывается только из сборки серверного приложения, проверка форм не учитывает атрибуты валидации моделей.

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

Для проверки модели, определённой в проекте .ClientBlazor Web App:

  • Создайте в проекте IServiceCollection метод, который принимает экземпляр .Client в качестве аргумента и вызывает для него AddValidation.
  • В приложении вызовите как метод, так и AddValidation.

Предыдущий подход приводит к проверке типов из обеих сборок.

В следующем примере метод AddValidationForClientTypes создается для проекта .Client валидации с использованием типов, определяемых в проекте Blazor Web App.

ServiceCollectionExtensions.cs в проекте .Client, который определяет типы, подлежащие проверке, и использует пример пространства имен BlazorSample.Client.Extensions:

namespace BlazorSample.Client.Extensions;

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddValidationForClientTypes(
        this IServiceCollection services)
    {
        return services.AddValidation();
    }
}

В файле Program серверного проекта:

  • Вызовите метод расширения коллекции служб проекта .Client, чтобы проверить типы в проекте .Client.
  • Вызов AddValidation для проверки типов в серверном проекте.
using BlazorSample.Client.Extensions;

...

builder.Services.AddValidationForClientTypes();
builder.Services.AddValidation();

Экспериментальный API в приложениях, предназначенных для .NET 10

Атрибуты из Microsoft.Extensions.Validation пакета NuGet ( и SkipValidationAttribute) помечены как ValidatableTypeAttribute в .NET 10. Пакет предназначен для предоставления новой общей инфраструктуры для функций проверки в разных платформах, а публикация экспериментальных типов обеспечивает большую гибкость для окончательного проектирования общедоступного API для повышения поддержки в использовании платформ. По состоянию на .NET 11 атрибуты больше не экспериментальны, поэтому рекомендации в этом разделе не применяются к приложениям, предназначенным для .NET 11 или более поздней версии.

В Blazor приложениях типы становятся доступными с помощью созданного внедренного атрибута. Если веб-приложение project, использующее пакет SDK () или RCL, использующий пакет SDK () содержит компоненты (), платформа автоматически создает внутренний атрибут внутри project (, ). Эти типы взаимозаменяемы с фактическими атрибутами и не помечены экспериментальными. В большинстве случаев разработчики используют [ValidatableType]/[SkipValidation] атрибуты для своих классов без беспокойства по поводу их источника.

Однако приведенный выше подход не подходит в библиотеках обычных классов, использующих пакет SDK Microsoft.NET.Sdk (<Project Sdk="Microsoft.NET.Sdk">). Использование типов в обычной библиотеке классов приводит к предупреждению анализа кода:

ASP0029: "Microsoft.Extensions.Validation.ValidatableTypeAttribute" предназначен только для оценки и подлежит изменению или удалению в будущих обновлениях. Отключайте эту диагностику, чтобы продолжить.

Предупреждение можно отключить с помощью любого из следующих подходов:

  • Свойство <NoWarn> в файле проекта:

    <PropertyGroup>
      <NoWarn>$(NoWarn);ASP0029</NoWarn>
    </PropertyGroup>
    
  • Директиваpragma, в которой используется атрибут:

    #pragma warning disable ASP0029
    [Microsoft.Extensions.Validation.ValidatableType]
    #pragma warning restore ASP0029
    
  • Правило для файла EditorConfig (.editorconfig)

    dotnet_diagnostic.ASP0029.severity = none
    

Если отключение предупреждения неприемлемо, вручную создайте встраиваемый атрибут в библиотеке, которую веб-библиотеки и Razor пакеты SDK генерируют автоматически.

ValidatableTypeAttribute.cs:

namespace Microsoft.Extensions.Validation.Embedded
{
    [AttributeUsage(AttributeTargets.Class)]
    internal sealed class ValidatableTypeAttribute : Attribute
    {
    }
}

Используйте точное пространство имен (Microsoft.Extensions.Validation.Embedded) и имя класса (ValidatableTypeAttribute), чтобы генератор источника проверки обнаружил и использовал тип. Вы можете объявить глобальную инструкцию using для пространства имен с помощью инструкции global using Microsoft.Extensions.Validation.Embedded; или элемента <Using Include="Microsoft.Extensions.Validation.Embedded" /> в файле проекта библиотеки.

Какой бы подход ни был принят, укажите наличие обходного решения для будущего обновления кода, когда приложение может использовать .NET 11 или более поздней версии. В то время вы можете удалить обходные пути из приложения.

Проверяемые сущности

Можно проверить три типа сущностей:

Проверка параметров

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

  1. Проверяйте экземпляры ValidationAttribute, применяемые к параметру Minimal API.
  2. Если тип параметра — IEnumerable, проверьте тип для всех элементов, кроме null. В противном случае проверьте тип значения.

Note

До выпуска .NET 11 существует известное ограничение, в котором типы значений null, объявленные как минимальные параметры API, не проверяются. Дополнительные сведения см. в разделе Атрибуты проверки игнорируются для типов значений, допускающих значение null, при передаче значения null (dotnet/aspnetcore #67033).

Проверка типов

Проверка типа является следующим шагом после проверки параметров (и является первым шагом в Blazor). Для этого необходимо выполнить следующие действия.

  1. Проверьте свойства типа. Если обнаружены ошибки, процесс проверки останавливается.
  2. Проверьте экземпляры на уровне типа ValidationAttribute. Если обнаружены ошибки, процесс проверки останавливается.
  3. Проверьте реализации IValidatableObject.

Проверка свойств

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

  1. Проверить экземпляры ValidationAttribute, примененные к свойству.
  2. Если значение свойства равно IEnumerable, выполните проверку типа для всех элементов, кроме null. В противном случае выполните однократную проверку типа для значения.

Явный пропуск проверки

При необходимости можно пропустить проверку для определенного параметра, типа или свойства, применяя его SkipValidationAttribute.

Принудительно создать сведения о типе, поддающемся проверке на корректность

Microsoft.Extensions.Validation работает через генератор исходного кода Roslyn, который определяет граф объектов и типы для параметров конечных точек Minimal API.

В некоторых случаях во время компиляции можно определить не все типы, которые являются частью графа объектов. В таких случаях можно заставить генератор исходного кода учитывать тип при проверке, применив ValidatableTypeAttribute к этому типу.

Поддержка асинхронной проверки

Microsoft.Extensions.Validation поддерживает асинхронную проверку. Пользовательские реализации AsyncValidationAttribute можно применять к параметрам, типам и свойствам, и они вызываются асинхронно. Кроме того, типы также могут реализовывать IAsyncValidatableObject.

При проверке свойств типа все задачи проверки запускаются одновременно. Аналогичным образом элементы коллекций IEnumerable проверяются одновременно.

IAsyncValidatableObject и AsyncValidationAttribute требуют синхронной и асинхронной логики валидации. Например, для объектов ValidateAsync, использующих этот интерфейс, должны быть реализованы методы Validate и IAsyncValidatableObject. Однако проверка никогда не вызывает оба метода. Если проверка вызывается через асинхронный путь кода, вызывается только ValidateAsync . Если проверка вызывается через синхронный путь кода, вызывается только Validate .

При валидации в Minimal API Microsoft.Extensions.Validation всегда использует асинхронный код и никогда — синхронный.

BlazorПроверка формы использует синхронный путь выполнения через метод EditContext.Validate, устаревший начиная с .NET 11.

Если реализация не поддерживает синхронный путь, вызовите InvalidOperationException.

В следующем примере показан класс проверки, реализующий IAsyncValidatableObject интерфейс. В следующем сценарии проверка требует асинхронного пути вызова для проверки базы данных допустимого имени пользователя электронной почты с помощью гипотетической IUserService службы. Поскольку в этом сценарии для проверки требуется асинхронный вызов базы данных, синхронный метод Validate, который предусмотрен контрактом интерфейса, не должен вызываться кодом разработчика в других местах и, если это всё же происходит, генерирует исключение InvalidOperationException.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Threading;
using System.Threading.Tasks;

public class ValidateUser : IAsyncValidatableObject
{
    [Required, EmailAddress]
    public string Email { get; set; } = string.Empty;

    // Asynchronous validation path
    public async IAsyncEnumerable<ValidationResult> ValidateAsync(
        ValidationContext validationContext, 
        [EnumeratorCancellation] CancellationToken cancellationToken = default)
    {
        var userService = validationContext.GetService<IUserService>();

        if (userService is not null)
        {
            // Asynchronous call that checks a database via a service
            if (await userService.IsEmailExistsAsync(Email, cancellationToken))
            {
                yield return new ValidationResult(
                    "Email is already registered.", new[] { nameof(Email) });
            }
        }
    }

    // Synchronous validation path that throws InvalidOperationException
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        throw new InvalidOperationException("Synchronous validation isn't supported.");
    }
}

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