Поделиться через


Blazor проверка основных форм ASP.NET

Примечание.

Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 9 этой статьи.

Предупреждение

Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в политике поддержки .NET и .NET Core. В текущем выпуске см . версию .NET 9 этой статьи.

Внимание

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

В текущем выпуске см . версию .NET 9 этой статьи.

В этой статье объясняется, как использовать проверку в Blazor формах.

Проверка формы

В базовых сценариях проверки формы экземпляр EditForm может использовать для проверки полей формы объявленные экземпляры EditContext и ValidationMessageStore. Обработчик для события OnValidationRequested объекта EditContext выполняет настраиваемую логику проверки. Результат обработчика обновляет экземпляр ValidationMessageStore.

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

Для Blazor Web Appпроверки на стороне клиента требуется активный BlazorSignalR канал. Проверка на стороне клиента недоступна для форм в компонентах, которые приняли статическую отрисовку на стороне сервера (статический SSR). Формы, использующие статический SSR, проверяются на сервере после отправки формы.

В следующем компоненте HandleValidationRequested метод обработчика очищает все существующие сообщения проверки путем вызова ValidationMessageStore.Clear перед проверкой формы.

Starship8.razor:

@page "/starship-8"
@implements IDisposable
@inject ILogger<Starship8> Logger

<h2>Holodeck Configuration</h2>

<EditForm EditContext="editContext" OnValidSubmit="Submit" FormName="Starship8">
    <div>
        <label>
            <InputCheckbox @bind-Value="Model!.Subsystem1" />
            Safety Subsystem
        </label>
    </div>
    <div>
        <label>
            <InputCheckbox @bind-Value="Model!.Subsystem2" />
            Emergency Shutdown Subsystem
        </label>
    </div>
    <div>
        <ValidationMessage For="() => Model!.Options" />
    </div>
    <div>
        <button type="submit">Update</button>
    </div>
</EditForm>

@code {
    private EditContext? editContext;

    [SupplyParameterFromForm]
    private Holodeck? Model { get; set; }

    private ValidationMessageStore? messageStore;

    protected override void OnInitialized()
    {
        Model ??= new();
        editContext = new(Model);
        editContext.OnValidationRequested += HandleValidationRequested;
        messageStore = new(editContext);
    }

    private void HandleValidationRequested(object? sender,
        ValidationRequestedEventArgs args)
    {
        messageStore?.Clear();

        // Custom validation logic
        if (!Model!.Options)
        {
            messageStore?.Add(() => Model.Options, "Select at least one.");
        }
    }

    private void Submit() => Logger.LogInformation("Submit: Processing form");

    public class Holodeck
    {
        public bool Subsystem1 { get; set; }
        public bool Subsystem2 { get; set; }
        public bool Options => Subsystem1 || Subsystem2;
    }

    public void Dispose()
    {
        if (editContext is not null)
        {
            editContext.OnValidationRequested -= HandleValidationRequested;
        }
    }
}
@page "/starship-8"
@implements IDisposable
@inject ILogger<Starship8> Logger

<h2>Holodeck Configuration</h2>

<EditForm EditContext="editContext" OnValidSubmit="Submit" FormName="Starship8">
    <div>
        <label>
            <InputCheckbox @bind-Value="Model!.Subsystem1" />
            Safety Subsystem
        </label>
    </div>
    <div>
        <label>
            <InputCheckbox @bind-Value="Model!.Subsystem2" />
            Emergency Shutdown Subsystem
        </label>
    </div>
    <div>
        <ValidationMessage For="() => Model!.Options" />
    </div>
    <div>
        <button type="submit">Update</button>
    </div>
</EditForm>

@code {
    private EditContext? editContext;

    [SupplyParameterFromForm]
    private Holodeck? Model { get; set; }

    private ValidationMessageStore? messageStore;

    protected override void OnInitialized()
    {
        Model ??= new();
        editContext = new(Model);
        editContext.OnValidationRequested += HandleValidationRequested;
        messageStore = new(editContext);
    }

    private void HandleValidationRequested(object? sender,
        ValidationRequestedEventArgs args)
    {
        messageStore?.Clear();

        // Custom validation logic
        if (!Model!.Options)
        {
            messageStore?.Add(() => Model.Options, "Select at least one.");
        }
    }

    private void Submit() => Logger.LogInformation("Submit: Processing form");

    public class Holodeck
    {
        public bool Subsystem1 { get; set; }
        public bool Subsystem2 { get; set; }
        public bool Options => Subsystem1 || Subsystem2;
    }

    public void Dispose()
    {
        if (editContext is not null)
        {
            editContext.OnValidationRequested -= HandleValidationRequested;
        }
    }
}
@page "/starship-8"
@implements IDisposable
@inject ILogger<Starship8> Logger

<h2>Holodeck Configuration</h2>

<EditForm EditContext="editContext" OnValidSubmit="Submit">
    <div>
        <label>
            <InputCheckbox @bind-Value="Model!.Subsystem1" />
            Safety Subsystem
        </label>
    </div>
    <div>
        <label>
            <InputCheckbox @bind-Value="Model!.Subsystem2" />
            Emergency Shutdown Subsystem
        </label>
    </div>
    <div>
        <ValidationMessage For="() => Model!.Options" />
    </div>
    <div>
        <button type="submit">Update</button>
    </div>
</EditForm>

@code {
    private EditContext? editContext;

    public Holodeck? Model { get; set; }

    private ValidationMessageStore? messageStore;

    protected override void OnInitialized()
    {
        Model ??= new();
        editContext = new(Model);
        editContext.OnValidationRequested += HandleValidationRequested;
        messageStore = new(editContext);
    }

    private void HandleValidationRequested(object? sender,
        ValidationRequestedEventArgs args)
    {
        messageStore?.Clear();

        // Custom validation logic
        if (!Model!.Options)
        {
            messageStore?.Add(() => Model.Options, "Select at least one.");
        }
    }

    private void Submit()
    {
        Logger.LogInformation("Submit called: Processing the form");
    }

    public class Holodeck
    {
        public bool Subsystem1 { get; set; }
        public bool Subsystem2 { get; set; }
        public bool Options => Subsystem1 || Subsystem2;
    }

    public void Dispose()
    {
        if (editContext is not null)
        {
            editContext.OnValidationRequested -= HandleValidationRequested;
        }
    }
}

Компонент проверяющего элемента управления для заметок к данным и пользовательская проверка

Компонент DataAnnotationsValidator привязывает проверку заметок к данным к каскадному значению EditContext. Для включения проверки заметок к данным требуется компонент DataAnnotationsValidator. Чтобы использовать другую систему проверки, а не заметки к данным, используйте вместо компонента DataAnnotationsValidator пользовательскую реализацию. Реализации платформы для DataAnnotationsValidator можно просмотреть в справочных материалах:

Примечание.

По ссылкам в документации на справочные материалы по .NET обычно загружается ветвь репозитория по умолчанию, которая представляет текущую разработку для следующего выпуска .NET. Чтобы выбрать тег для определенного выпуска, используйте раскрывающийся список Switch branches or tags (Переключение ветвей или тегов). Дополнительные сведения см. в статье Выбор тега версии исходного кода ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Blazor выполняет два типа проверки данных:

  • Проверка поля выполняется, когда пользователь выходит за пределы поля. Во время проверки поля компонент DataAnnotationsValidator связывает все результаты проверки с полем.
  • Проверка модели выполняется, когда пользователь отправляет форму. Во время проверки модели компонент DataAnnotationsValidator пытается определить поле на основе имени члена из результатов проверки. Результаты проверки, не связанные с отдельным элементом, связаны с моделью, а не с полем.

Компоненты проверяющего элемента управления

Компоненты проверяющего элемента управления поддерживают проверку форм путем управления ValidationMessageStore для класса EditContext формы.

Платформа Blazor предоставляет компонент DataAnnotationsValidator для прикрепления к формам поддержки проверки на основе атрибутов проверки (заметок к данным). Вы можете создать настраиваемые компоненты проверяющего средства для обработки сообщений проверки для разных форм на одной странице или одной и той же форме на различных этапах обработки форм (например, проверка клиента, за которой следует проверка сервера). Показанный в этом разделе пример компонента проверяющего элемента управления (CustomValidation) используется в следующих разделах этой статьи.

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

Примечание.

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

Создайте компонент проверяющего элемента управления из класса ComponentBase.

  • Класс EditContext формы является каскадным параметром компонента.
  • При инициализации компонента проверяющего элемента управления создается новый класс ValidationMessageStore для поддержки текущего списка ошибок формы.
  • Хранилище сообщений получает ошибки, когда код разработчика в компоненте формы вызывает метод DisplayErrors. Ошибки передаются в метод DisplayErrors в классе Dictionary<string, List<string>>. В словаре ключом является имя поля формы, в котором есть одна ошибка или несколько. Значением является список ошибок.
  • Сообщения очищаются при возникновении любого из следующих действий:
    • В EditContext запрашивается проверка при возникновении события OnValidationRequested. Все ошибки очищаются.
    • В форме изменяется поле при возникновении события OnFieldChanged. Очищаются только ошибки для этого поля.
    • В коде разработчика вызывается метод ClearErrors. Все ошибки очищаются.

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

CustomValidation.cs:

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;

namespace BlazorSample;

public class CustomValidation : ComponentBase
{
    private ValidationMessageStore? messageStore;

    [CascadingParameter]
    private EditContext? CurrentEditContext { get; set; }

    protected override void OnInitialized()
    {
        if (CurrentEditContext is null)
        {
            throw new InvalidOperationException(
                $"{nameof(CustomValidation)} requires a cascading " +
                $"parameter of type {nameof(EditContext)}. " +
                $"For example, you can use {nameof(CustomValidation)} " +
                $"inside an {nameof(EditForm)}.");
        }

        messageStore = new(CurrentEditContext);

        CurrentEditContext.OnValidationRequested += (s, e) => 
            messageStore?.Clear();
        CurrentEditContext.OnFieldChanged += (s, e) => 
            messageStore?.Clear(e.FieldIdentifier);
    }

    public void DisplayErrors(Dictionary<string, List<string>> errors)
    {
        if (CurrentEditContext is not null)
        {
            foreach (var err in errors)
            {
                messageStore?.Add(CurrentEditContext.Field(err.Key), err.Value);
            }

            CurrentEditContext.NotifyValidationStateChanged();
        }
    }

    public void ClearErrors()
    {
        messageStore?.Clear();
        CurrentEditContext?.NotifyValidationStateChanged();
    }
}

Внимание

Указание пространства имен является обязательным при наследовании от ComponentBase. Если не указать пространство имен, это приведет к ошибке сборки:

Tag helpers cannot target tag name '<global namespace>.{CLASS NAME}' because it contains a ' ' character.

Заполнитель {CLASS NAME} — это имя класса компонента. В примере пользовательского проверяющего элемента управления в этом разделе указано пространство имен примера BlazorSample.

Примечание.

Анонимные лямбда-выражения — это зарегистрированные обработчики событий для OnValidationRequested и OnFieldChanged в предыдущем примере. В этом сценарии нет необходимости в реализации IDisposable и отключении делегатов событий. Дополнительные сведения см. в статье Жизненный цикл компонентов Razor ASP.NET Core.

Проверка бизнес-логики с помощью компонента проверяющего элемента управления

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

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

В следующем примере :

  • Сокращенная версия Starfleet Starship Database формы (Starship3компонента) раздела формы "Пример" статьи "Входные компоненты" используется, которая принимает только классификацию и описание звездочки. Проверка заметки данных не активируется при отправке формы, так как DataAnnotationsValidator компонент не включен в форму.
  • Используется компонент CustomValidation из раздела Компоненты проверяющего элемента управления этой статьи.
  • Проверка требует значение для описания корабля (Description), если пользователь выбирает классификацию корабля Defense (Classification).

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

Starship9.razor:

@page "/starship-9"
@inject ILogger<Starship9> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm Model="Model" OnValidSubmit="Submit" FormName="Starship9">
    <CustomValidation @ref="customValidation" />
    <ValidationSummary />
    <div>
        <label>
            Primary Classification:
            <InputSelect @bind-Value="Model!.Classification">
                <option value="">
                    Select classification ...
                </option>
                <option checked="@(Model!.Classification == "Exploration")" 
                    value="Exploration">
                    Exploration
                </option>
                <option checked="@(Model!.Classification == "Diplomacy")" 
                    value="Diplomacy">
                    Diplomacy
                </option>
                <option checked="@(Model!.Classification == "Defense")" 
                    value="Defense">
                    Defense
                </option>
            </InputSelect>
        </label>
    </div>
    <div>
        <label>
            Description (optional):
            <InputTextArea @bind-Value="Model!.Description" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

@code {
    private CustomValidation? customValidation;

    [SupplyParameterFromForm]
    private Starship? Model { get; set; }

    protected override void OnInitialized() =>
        Model ??= new() { ProductionDate = DateTime.UtcNow };

    private void Submit()
    {
        customValidation?.ClearErrors();

        var errors = new Dictionary<string, List<string>>();

        if (Model!.Classification == "Defense" &&
                string.IsNullOrEmpty(Model.Description))
        {
            errors.Add(nameof(Model.Description),
                new() { "For a 'Defense' ship classification, " +
                "'Description' is required." });
        }

        if (errors.Any())
        {
            customValidation?.DisplayErrors(errors);
        }
        else
        {
            Logger.LogInformation("Submit called: Processing the form");
        }
    }
}
@page "/starship-9"
@inject ILogger<Starship9> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm Model="Model" OnValidSubmit="Submit" FormName="Starship9">
    <CustomValidation @ref="customValidation" />
    <ValidationSummary />
    <div>
        <label>
            Primary Classification:
            <InputSelect @bind-Value="Model!.Classification">
                <option value="">
                    Select classification ...
                </option>
                <option checked="@(Model!.Classification == "Exploration")" 
                    value="Exploration">
                    Exploration
                </option>
                <option checked="@(Model!.Classification == "Diplomacy")" 
                    value="Diplomacy">
                    Diplomacy
                </option>
                <option checked="@(Model!.Classification == "Defense")" 
                    value="Defense">
                    Defense
                </option>
            </InputSelect>
        </label>
    </div>
    <div>
        <label>
            Description (optional):
            <InputTextArea @bind-Value="Model!.Description" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

@code {
    private CustomValidation? customValidation;

    [SupplyParameterFromForm]
    private Starship? Model { get; set; }

    protected override void OnInitialized() =>
        Model ??= new() { ProductionDate = DateTime.UtcNow };

    private void Submit()
    {
        customValidation?.ClearErrors();

        var errors = new Dictionary<string, List<string>>();

        if (Model!.Classification == "Defense" &&
                string.IsNullOrEmpty(Model.Description))
        {
            errors.Add(nameof(Model.Description),
                new() { "For a 'Defense' ship classification, " +
                "'Description' is required." });
        }

        if (errors.Any())
        {
            customValidation?.DisplayErrors(errors);
        }
        else
        {
            Logger.LogInformation("Submit called: Processing the form");
        }
    }
}
@page "/starship-9"
@inject ILogger<Starship9> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm Model="Model" OnValidSubmit="Submit">
    <CustomValidation @ref="customValidation" />
    <ValidationSummary />
    <div>
        <label>
            Primary Classification:
            <InputSelect @bind-Value="Model!.Classification">
                <option value="">Select classification ...</option>
                <option value="Exploration">Exploration</option>
                <option value="Diplomacy">Diplomacy</option>
                <option value="Defense">Defense</option>
            </InputSelect>
        </label>
    </div>
    <div>
        <label>
            Description (optional):
            <InputTextArea @bind-Value="Model!.Description" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

@code {
    private CustomValidation? customValidation;

    public Starship? Model { get; set; }

    protected override void OnInitialized() =>
        Model ??= new() { ProductionDate = DateTime.UtcNow };

    private void Submit()
    {
        customValidation?.ClearErrors();

        var errors = new Dictionary<string, List<string>>();

        if (Model!.Classification == "Defense" &&
                string.IsNullOrEmpty(Model.Description))
        {
            errors.Add(nameof(Model.Description),
                new() { "For a 'Defense' ship classification, " +
                "'Description' is required." });
        }

        if (errors.Any())
        {
            customValidation?.DisplayErrors(errors);
        }
        else
        {
            Logger.LogInformation("Submit called: Processing the form");
        }
    }
}

Примечание.

Вместо компонентов проверки можно использовать атрибуты проверки заметок к данным. Настраиваемые атрибуты, применяемые к модели формы, активируются с помощью компонента DataAnnotationsValidator. При использовании с проверкой сервера атрибуты должны быть исполняемыми на сервере. Дополнительные сведения см. в разделе "Настраиваемые атрибуты проверки".

Проверка сервера с помощью компонента проверяющего элемента управления

Этот раздел посвящен Blazor Web App сценариям, но подход для любого типа приложения, использующего проверку сервера с веб-API, принимает тот же общий подход.

Этот раздел посвящен размещенным Blazor WebAssembly сценариям, но подход для любого типа приложения, использующего проверку сервера с веб-API, принимает тот же общий подход.

Проверка сервера поддерживается в дополнение к проверке клиента:

  • Обработать проверку клиента в форме с DataAnnotationsValidator помощью компонента.
  • Когда форма передает проверку клиента (OnValidSubmit вызывается), отправьте EditContext.Model API серверного сервера для обработки форм.
  • Обрабатывайте проверку модели на сервере.
  • Серверный API включает как встроенную платформенную проверку заметок к данным, так и настраиваемую логику проверки, предоставленную разработчиком. Если проверка на сервере пройдена, выполните обработку формы и отправьте обратно код состояния успеха (200 - OK). Если проверка завершается неудачно, возвращайте код состояния сбоя (400 - Bad Request) и ошибки проверки полей.
  • Отключите форму в случае успешного выполнения или отобразите ошибки.

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

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

Поместите Starship модель (Starship.cs) в проект библиотеки общих классов, чтобы клиентские и серверные проекты могли использовать модель. Добавьте или обновите пространство имен, чтобы оно соответствовало пространству имен общего приложения (например, namespace BlazorSample.Shared). Так как для модели требуются заметки данных, убедитесь, что библиотека общих классов использует общую платформу или добавляет System.ComponentModel.Annotations пакет в общий проект.

Примечание.

Рекомендации по добавлению пакетов в приложения .NET см. в разделе Способы установки пакетов NuGet в статье Рабочий процесс использования пакета (документация по NuGet). Проверьте правильность версий пакета на сайте NuGet.org.

В главном проекте Blazor Web Appдобавьте контроллер для обработки запросов на проверку звездохода и возврата сообщений о сбое проверки. Обновите пространства имен в последней using инструкции для проекта библиотеки общих классов и namespace класса контроллера. Помимо проверки заметок данных клиента и сервера контроллер проверяет, предоставляется ли значение для описания корабля (Description), если пользователь выбирает Defense классификацию кораблей (Classification).

Поместите модель Starship (Starship.cs) в проект решения Shared, чтобы эту модель могли использовать клиентские и серверные приложения. Добавьте или обновите пространство имен, чтобы оно соответствовало пространству имен общего приложения (например, namespace BlazorSample.Shared). Так как для модели требуются заметки к данным, добавьте пакет System.ComponentModel.Annotations в проект Shared.

Примечание.

Рекомендации по добавлению пакетов в приложения .NET см. в разделе Способы установки пакетов NuGet в статье Рабочий процесс использования пакета (документация по NuGet). Проверьте правильность версий пакета на сайте NuGet.org.

В проекте Server добавьте контроллер для обработки запросов проверки космического корабля и возвращайте сообщения о неудачной проверке. Обновите пространства имен в последней инструкции using для проекта Shared и namespace для класса контроллера. Помимо проверки заметок данных клиента и сервера контроллер проверяет, предоставляется ли значение для описания корабля (Description), если пользователь выбирает Defense классификацию кораблей (Classification).

Проверка Defense классификации корабля выполняется только на сервере в контроллере, так как следующая форма не выполняет ту же клиентскую проверку на стороне клиента проверки, когда форма отправляется на сервер. Проверка сервера без проверки клиента распространена в приложениях, требующих проверки частной бизнес-логики входных данных пользователя на сервере. Например, для проверки данных, введенных пользователем, может понадобиться конфиденциальная информация из данных, хранящихся для пользователя. Частные данные, очевидно, не могут быть отправлены клиенту для проверки клиента.

Примечание.

Контроллер StarshipValidation в этом разделе использует Microsoft Identity 2.0. Веб-интерфейс API принимает маркеры только для пользователей с областью API.Access для этого API. Дополнительная настройка необходима, если имя области API отличается от API.Access.

Дополнительные сведения о безопасности:

Controllers/StarshipValidation.cs:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using BlazorSample.Shared;

namespace BlazorSample.Server.Controllers;

[Authorize]
[ApiController]
[Route("[controller]")]
public class StarshipValidationController(
    ILogger<StarshipValidationController> logger) 
    : ControllerBase
{
    static readonly string[] scopeRequiredByApi = new[] { "API.Access" };

    [HttpPost]
    public async Task<IActionResult> Post(Starship model)
    {
        HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);

        try
        {
            if (model.Classification == "Defense" && 
                string.IsNullOrEmpty(model.Description))
            {
                ModelState.AddModelError(nameof(model.Description),
                    "For a 'Defense' ship " +
                    "classification, 'Description' is required.");
            }
            else
            {
                logger.LogInformation("Processing the form asynchronously");

                // async ...

                return Ok(ModelState);
            }
        }
        catch (Exception ex)
        {
            logger.LogError("Validation Error: {Message}", ex.Message);
        }

        return BadRequest(ModelState);
    }
}

Подтвердите или обновите пространство имен предыдущего контроллера (BlazorSample.Server.Controllers), чтобы соответствовать пространству имен контроллеров приложения.

Если на сервере возникает ошибка проверки привязки модели, то ApiController (ApiControllerAttribute) обычно возвращает ответ о неверном запросе по умолчанию с ValidationProblemDetails. Ответ содержит больше данных, чем только ошибки проверки, как показано в следующем примере, когда все поля Starfleet Starship Database формы не отправлены, и форма завершается ошибкой проверки:

{
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "Id": ["The Id field is required."],
    "Classification": ["The Classification field is required."],
    "IsValidatedDesign": ["This form disallows unapproved ships."],
    "MaximumAccommodation": ["Accommodation invalid (1-100000)."]
  }
}

Примечание.

Чтобы продемонстрировать предыдущий ответ JSON, необходимо отключить проверку клиента формы, чтобы разрешить пустую отправку формы поля или использовать средство для отправки запроса непосредственно в API сервера, например Firefox Browser Developer.

Если серверный API возвращает предыдущий ответ JSON по умолчанию, клиент может проанализировать ответ в коде разработчика, чтобы получить дочерние элементы узла errors для обработки ошибок проверки форм. Писать код разработчика для анализа файла затруднительно. Для анализа JSON вручную необходимо создать Dictionary<string, List<string>> с ошибками после вызова метода ReadFromJsonAsync. В идеале API сервера должен возвращать только ошибки проверки, как показано в следующем примере:

{
  "Id": ["The Id field is required."],
  "Classification": ["The Classification field is required."],
  "IsValidatedDesign": ["This form disallows unapproved ships."],
  "MaximumAccommodation": ["Accommodation invalid (1-100000)."]
}

Чтобы изменить ответ API сервера, чтобы он возвращал только ошибки проверки, измените делегат, который вызывается для действий, которые аннотированы в ApiControllerAttribute Program файле. Для конечной точки API (/StarshipValidation) возвращайте BadRequestObjectResult с помощью ModelStateDictionary. Для других конечных точек API сохраните поведение по умолчанию, возвращая результат объекта с помощью нового ValidationProblemDetails.

Microsoft.AspNetCore.Mvc Добавьте пространство имен в начало Program файла в основном проектеBlazor Web App:

using Microsoft.AspNetCore.Mvc;

Program В файле добавьте или обновите следующий AddControllersWithViews метод расширения и добавьте следующий вызовConfigureApiBehaviorOptions:

builder.Services.AddControllersWithViews()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.InvalidModelStateResponseFactory = context =>
        {
            if (context.HttpContext.Request.Path == "/StarshipValidation")
            {
                return new BadRequestObjectResult(context.ModelState);
            }
            else
            {
                return new BadRequestObjectResult(
                    new ValidationProblemDetails(context.ModelState));
            }
        };
    });

Если вы добавляете контроллеры в основной проект в первый раз, сопоставляйте конечные точки контроллера при создании предыдущего Blazor Web App кода, который регистрирует службы для контроллеров. В следующем примере используются маршруты контроллера по умолчанию:

app.MapDefaultControllerRoute();

Примечание.

В предыдущем примере явным образом регистрируются службы контроллеров путем вызова AddControllersWithViews автоматического устранения атак межсайтовых запросов forgery (XSRF/CSRF). Если вы просто используете AddControllers, антифоргерия не включена автоматически.

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

В проекте .Client добавьте компонент CustomValidation, показанный в разделе Компоненты проверяющего элемента управления. Обновите пространство имен, чтобы оно соответствовало приложению (например, namespace BlazorSample.Client).

В проекте .Client форма Starfleet Starship Database обновляется для отображения ошибок серверной проверки с помощью компонента CustomValidation. Когда серверный API возвращает сообщения проверки, они добавляются в ValidationMessageStore компонента CustomValidation. Эти ошибки можно вывести из EditContext формы с помощью сводки проверки формы.

В следующем компоненте обновите пространство имен общего проекта (@using BlazorSample.Shared) до пространства имен общего проекта. Обратите внимание, что форма требует авторизации, поэтому пользователь должен войти в приложение для перехода к форме.

Добавьте пространство имен Microsoft.AspNetCore.Mvc в начало файла Program в приложении Server:

using Microsoft.AspNetCore.Mvc;

Program В файле найдите AddControllersWithViews метод расширения и добавьте следующий вызовConfigureApiBehaviorOptions:

builder.Services.AddControllersWithViews()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.InvalidModelStateResponseFactory = context =>
        {
            if (context.HttpContext.Request.Path == "/StarshipValidation")
            {
                return new BadRequestObjectResult(context.ModelState);
            }
            else
            {
                return new BadRequestObjectResult(
                    new ValidationProblemDetails(context.ModelState));
            }
        };
    });

Примечание.

В предыдущем примере явным образом регистрируются службы контроллеров путем вызова AddControllersWithViews автоматического устранения атак межсайтовых запросов forgery (XSRF/CSRF). Если вы просто используете AddControllers, антифоргерия не включена автоматически.

В проекте Client добавьте компонент CustomValidation, показанный в разделе Компоненты проверяющего элемента управления. Обновите пространство имен, чтобы оно соответствовало приложению (например, namespace BlazorSample.Client).

В проекте Client форма Starfleet Starship Database обновляется для отображения ошибок серверной проверки с помощью компонента CustomValidation. Когда серверный API возвращает сообщения проверки, они добавляются в ValidationMessageStore компонента CustomValidation. Эти ошибки можно вывести из EditContext формы с помощью сводки проверки формы.

В следующем компоненте обновите пространство Shared имен проекта (@using BlazorSample.Shared) до общего пространства имен проекта. Обратите внимание, что форма требует авторизации, поэтому пользователь должен войти в приложение для перехода к форме.

Starship10.razor:

Примечание.

Формы на EditForm основе автоматической поддержки антифоргерии. Контроллер должен использовать AddControllersWithViews для регистрации служб контроллера и автоматической поддержки антифоргерии для веб-API.

@page "/starship-10"
@rendermode InteractiveWebAssembly
@using System.Net
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using BlazorSample.Shared
@attribute [Authorize]
@inject HttpClient Http
@inject ILogger<Starship10> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm FormName="Starship10" Model="Model" OnValidSubmit="Submit">
    <DataAnnotationsValidator />
    <CustomValidation @ref="customValidation" />
    <ValidationSummary />
    <div>
        <label>
            Identifier: 
            <InputText @bind-Value="Model!.Id" disabled="@disabled" />
        </label>
    </div>
    <div>
        <label>
            Description (optional):
            <InputTextArea @bind-Value="Model!.Description" 
                disabled="@disabled" />
        </label>
    </div>
    <div>
        <label>
            Primary Classification:
            <InputSelect @bind-Value="Model!.Classification" disabled="@disabled">
                <option value="">Select classification ...</option>
                <option value="Exploration">Exploration</option>
                <option value="Diplomacy">Diplomacy</option>
                <option value="Defense">Defense</option>
            </InputSelect>
        </label>
    </div>
    <div>
        <label>
            Maximum Accommodation:
            <InputNumber @bind-Value="Model!.MaximumAccommodation" 
                disabled="@disabled" />
        </label>
    </div>
    <div>
        <label>
            Engineering Approval:
            <InputCheckbox @bind-Value="Model!.IsValidatedDesign" 
                disabled="@disabled" />
        </label>
    </div>
    <div>
        <label>
            Production Date:
            <InputDate @bind-Value="Model!.ProductionDate" disabled="@disabled" />
        </label>
    </div>
    <div>
        <button type="submit" disabled="@disabled">Submit</button>
    </div>
    <div style="@messageStyles">
        @message
    </div>
</EditForm>

@code {
    private CustomValidation? customValidation;
    private bool disabled;
    private string? message;
    private string messageStyles = "visibility:hidden";

    [SupplyParameterFromForm]
    private Starship? Model { get; set; }

    protected override void OnInitialized() => 
        Model ??= new() { ProductionDate = DateTime.UtcNow };

    private async Task Submit(EditContext editContext)
    {
        customValidation?.ClearErrors();

        try
        {
            var response = await Http.PostAsJsonAsync<Starship>(
                "StarshipValidation", (Starship)editContext.Model);

            var errors = await response.Content
                .ReadFromJsonAsync<Dictionary<string, List<string>>>() ?? 
                new Dictionary<string, List<string>>();

            if (response.StatusCode == HttpStatusCode.BadRequest && 
                errors.Any())
            {
                customValidation?.DisplayErrors(errors);
            }
            else if (!response.IsSuccessStatusCode)
            {
                throw new HttpRequestException(
                    $"Validation failed. Status Code: {response.StatusCode}");
            }
            else
            {
                disabled = true;
                messageStyles = "color:green";
                message = "The form has been processed.";
            }
        }
        catch (AccessTokenNotAvailableException ex)
        {
            ex.Redirect();
        }
        catch (Exception ex)
        {
            Logger.LogError("Form processing error: {Message}", ex.Message);
            disabled = true;
            messageStyles = "color:red";
            message = "There was an error processing the form.";
        }
    }
}

.Client Проект должен Blazor Web App также зарегистрировать HttpClient http-запросы POST на контроллер веб-API серверной части. Подтвердите или добавьте следующее .Client в файл проекта Program :

builder.Services.AddScoped(sp => 
    new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

В предыдущем примере задается базовый адрес (IWebAssemblyHostEnvironment.BaseAddress), который получает базовый адрес builder.HostEnvironment.BaseAddress для приложения и обычно является производным от <base> значения тега href на хост-странице.

@page "/starship-10"
@using System.Net
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using BlazorSample.Shared
@attribute [Authorize]
@inject HttpClient Http
@inject ILogger<Starship10> Logger

<h1>Starfleet Starship Database</h1>

<h2>New Ship Entry Form</h2>

<EditForm Model="Model" OnValidSubmit="Submit">
    <DataAnnotationsValidator />
    <CustomValidation @ref="customValidation" />
    <ValidationSummary />
    <div>
        <label>
            Identifier: 
            <InputText @bind-Value="Model!.Id" disabled="@disabled" />
        </label>
    </div>
    <div>
        <label>
            Description (optional):
            <InputTextArea @bind-Value="Model!.Description" 
                disabled="@disabled" />
        </label>
    </div>
    <div>
        <label>
            Primary Classification:
            <InputSelect @bind-Value="Model!.Classification" disabled="@disabled">
                <option value="">Select classification ...</option>
                <option value="Exploration">Exploration</option>
                <option value="Diplomacy">Diplomacy</option>
                <option value="Defense">Defense</option>
            </InputSelect>
        </label>
    </div>
    <div>
        <label>
            Maximum Accommodation:
            <InputNumber @bind-Value="Model!.MaximumAccommodation" 
                disabled="@disabled" />
        </label>
    </div>
    <div>
        <label>
            Engineering Approval:
            <InputCheckbox @bind-Value="Model!.IsValidatedDesign" 
                disabled="@disabled" />
        </label>
    </div>
    <div>
        <label>
            Production Date:
            <InputDate @bind-Value="Model!.ProductionDate" disabled="@disabled" />
        </label>
    </div>
    <div>
        <button type="submit" disabled="@disabled">Submit</button>
    </div>
    <div style="@messageStyles">
        @message
    </div>
</EditForm>

@code {
    private CustomValidation? customValidation;
    private bool disabled;
    private string? message;
    private string messageStyles = "visibility:hidden";

    public Starship? Model { get; set; }

    protected override void OnInitialized() => 
        Model ??= new() { ProductionDate = DateTime.UtcNow };

    private async Task Submit(EditContext editContext)
    {
        customValidation?.ClearErrors();

        try
        {
            var response = await Http.PostAsJsonAsync<Starship>(
                "StarshipValidation", (Starship)editContext.Model);

            var errors = await response.Content
                .ReadFromJsonAsync<Dictionary<string, List<string>>>() ?? 
                new Dictionary<string, List<string>>();

            if (response.StatusCode == HttpStatusCode.BadRequest && 
                errors.Any())
            {
                customValidation?.DisplayErrors(errors);
            }
            else if (!response.IsSuccessStatusCode)
            {
                throw new HttpRequestException(
                    $"Validation failed. Status Code: {response.StatusCode}");
            }
            else
            {
                disabled = true;
                messageStyles = "color:green";
                message = "The form has been processed.";
            }
        }
        catch (AccessTokenNotAvailableException ex)
        {
            ex.Redirect();
        }
        catch (Exception ex)
        {
            Logger.LogError("Form processing error: {Message}", ex.Message);
            disabled = true;
            messageStyles = "color:red";
            message = "There was an error processing the form.";
        }
    }
}

Примечание.

Вместо компонента проверки можно использовать атрибуты проверки заметок к данным. Настраиваемые атрибуты, применяемые к модели формы, активируются с помощью компонента DataAnnotationsValidator. При использовании с проверкой сервера атрибуты должны быть исполняемыми на сервере. Дополнительные сведения см. в разделе "Настраиваемые атрибуты проверки".

Примечание.

Подход проверки сервера в этом разделе подходит для любого из примеров размещенного Blazor WebAssembly решения в этом наборе документации:

InputText на основе события ввода

Используйте компонент InputText, чтобы создать пользовательский компонент, использующий событие oninput (input), а не событие onchange (change). При каждом нажатии клавиши событие input активирует проверку поля.

Компонент CustomInputText ниже наследует компонент InputText платформы и задает привязку для события oninput (input).

CustomInputText.razor:

@inherits InputText

<input @attributes="AdditionalAttributes" 
       class="@CssClass" 
       @bind="CurrentValueAsString" 
       @bind:event="oninput" />

Компонент CustomInputText можно использовать везде, где используется InputText. Следующий компонент использует общий CustomInputText компонент.

Starship11.razor:

@page "/starship-11"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship11> Logger

<EditForm Model="Model" OnValidSubmit="Submit" FormName="Starship11">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <div>
        <label>
            Identifier: 
            <CustomInputText @bind-Value="Model!.Id" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

<div>
    CurrentValue: @Model?.Id
</div>

@code {
    [SupplyParameterFromForm]
    private Starship? Model { get; set; }

    protected override void OnInitialized() => Model ??= new();

    private void Submit() => Logger.LogInformation("Submit: Processing form");

    public class Starship
    {
        [Required]
        [StringLength(10, ErrorMessage = "Id is too long.")]
        public string? Id { get; set; }
    }
}
@page "/starship-11"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship11> Logger

<EditForm Model="Model" OnValidSubmit="Submit" FormName="Starship11">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <div>
        <label>
            Identifier: 
            <CustomInputText @bind-Value="Model!.Id" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

<div>
    CurrentValue: @Model?.Id
</div>

@code {
    [SupplyParameterFromForm]
    private Starship? Model { get; set; }

    protected override void OnInitialized() => Model ??= new();

    private void Submit() => Logger.LogInformation("Submit: Processing form");

    public class Starship
    {
        [Required]
        [StringLength(10, ErrorMessage = "Id is too long.")]
        public string? Id { get; set; }
    }
}
@page "/starship-11"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship11> Logger

<EditForm Model="Model" OnValidSubmit="Submit">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <CustomInputText @bind-Value="Model!.Id" />
    <button type="submit">Submit</button>
</EditForm>

<div>
    CurrentValue: @Model?.Id
</div>

@code {
    public Starship? Model { get; set; }

    protected override void OnInitialized() => Model ??= new();

    private void Submit()
    {
        Logger.LogInformation("Submit called: Processing the form");
    }

    public class Starship
    {
        [Required]
        [StringLength(10, ErrorMessage = "Id is too long.")]
        public string? Id { get; set; }
    }
}

Сводка проверки и компоненты сообщений о проверке

Компонент ValidationSummary содержит обзор всех сообщений проверки аналогично вспомогательному методу тега сводки проверки:

<ValidationSummary />

Выходные сообщения проверки для конкретной модели с параметром Model:

<ValidationSummary Model="Model" />

Компонент ValidationMessage<TValue> отображает сообщения проверки для определенного поля, которые похожи на вспомогательный метод тега сообщения проверки. Укажите поле для проверки с помощью атрибута For и лямбда-выражения с именем свойства модели:

<ValidationMessage For="@(() => Model!.MaximumAccommodation)" />

Компоненты ValidationMessage<TValue> и ValidationSummary поддерживают произвольные атрибуты. В созданный элемент <div> или <ul> добавляется любой атрибут, который не соответствует параметру компонента.

Вы можете управлять стилем сообщений проверки с помощью таблицы стилей приложения (wwwroot/css/app.css или wwwroot/css/site.css). Класс validation-message по умолчанию задает для сообщений проверки красный цвет текста:

.validation-message {
    color: red;
}

Определение допустимости поля формы

Используется EditContext.IsValid для определения допустимости поля без получения сообщений проверки.

Поддерживается, но не рекомендуется:

var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();

Рекомендованный:

var isValid = editContext.IsValid(fieldIdentifier);

Пользовательские атрибуты проверки

Чтобы убедиться, что результат проверки правильно связан с полем при использовании настраиваемого атрибута проверки, передайте MemberName контекста проверки при создании ValidationResult.

CustomValidator.cs:

using System;
using System.ComponentModel.DataAnnotations;

public class CustomValidator : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, 
        ValidationContext validationContext)
    {
        ...

        return new ValidationResult("Validation message to user.",
            new[] { validationContext.MemberName });
    }
}

Внедрите службы в пользовательские атрибуты проверки с помощью ValidationContext. В следующем примере показана форма для салата от шеф-повара, которая проверяет введенные пользователем данные с внедрением зависимостей (DI).

Класс SaladChef указывает утвержденный список ингредиентов звездочки для салата Ten Forward.

SaladChef.cs:

namespace BlazorSample;

public class SaladChef
{
    public string[] SaladToppers = { "Horva", "Kanda Root", "Krintar", "Plomeek",
        "Syto Bean" };
}

Зарегистрируйтесь SaladChef в контейнере DI приложения в Program файле:

builder.Services.AddTransient<SaladChef>();

Метод IsValid приведенного ниже класса SaladChefValidatorAttribute получает службу SaladChef из внедрения зависимостей для проверки данных, введенных пользователем.

SaladChefValidatorAttribute.cs:

using System.ComponentModel.DataAnnotations;

namespace BlazorSample;

public class SaladChefValidatorAttribute : ValidationAttribute
{
    protected override ValidationResult? IsValid(object? value,
        ValidationContext validationContext)
    {
        var saladChef = validationContext.GetRequiredService<SaladChef>();

        if (saladChef.SaladToppers.Contains(value?.ToString()))
        {
            return ValidationResult.Success;
        }

        return new ValidationResult("Is that a Vulcan salad topper?! " +
            "The following toppers are available for a Ten Forward salad: " +
            string.Join(", ", saladChef.SaladToppers));
    }
}

Следующий компонент проверяет входные данные пользователя, применяя SaladChefValidatorAttribute ([SaladChefValidator]) к строке ингредиента салата (SaladIngredient).

Starship12.razor:

@page "/starship-12"
@inject SaladChef SaladChef

<EditForm Model="this" autocomplete="off" FormName="Starship12">
    <DataAnnotationsValidator />
    <div>
        <label>
            Salad topper (@saladToppers):
            <input @bind="SaladIngredient" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
    <ul>
        @foreach (var message in context.GetValidationMessages())
        {
            <li class="validation-message">@message</li>
        }
    </ul>
</EditForm>

@code {
    private string? saladToppers;

    [SaladChefValidator]
    public string? SaladIngredient { get; set; }

    protected override void OnInitialized() =>
        saladToppers ??= string.Join(", ", SaladChef.SaladToppers);
}
@page "/starship-12"
@inject SaladChef SaladChef

<EditForm Model="this" autocomplete="off" FormName="Starship12">
    <DataAnnotationsValidator />
    <div>
        <label>
            Salad topper (@saladToppers):
            <input @bind="SaladIngredient" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
    <ul>
        @foreach (var message in context.GetValidationMessages())
        {
            <li class="validation-message">@message</li>
        }
    </ul>
</EditForm>

@code {
    private string? saladToppers;

    [SaladChefValidator]
    public string? SaladIngredient { get; set; }

    protected override void OnInitialized() =>
        saladToppers ??= string.Join(", ", SaladChef.SaladToppers);
}
@page "/starship-12"
@inject SaladChef SaladChef

<EditForm Model="this" autocomplete="off">
    <DataAnnotationsValidator />
    <p>
        <label>
            Salad topper (@saladToppers):
            <input @bind="SaladIngredient" />
        </label>
    </p>
    <button type="submit">Submit</button>
    <ul>
        @foreach (var message in context.GetValidationMessages())
        {
            <li class="validation-message">@message</li>
        }
    </ul>
</EditForm>

@code {
    private string? saladToppers;

    [SaladChefValidator]
    public string? SaladIngredient { get; set; }

    protected override void OnInitialized() => 
        saladToppers ??= string.Join(", ", SaladChef.SaladToppers);
}

Настраиваемые атрибуты класса проверки CSS

Настраиваемые атрибуты класса проверки CSS полезны при интеграции с платформами CSS, такими как Bootstrap.

Чтобы указать настраиваемые атрибуты класса проверки CSS, начните с предоставления стилей CSS для пользовательской проверки. В следующем примере указаны допустимый (validField) и недопустимый стили (invalidField):

Добавьте следующие классы CSS в таблицу стилей приложения:

.validField {
    border-color: lawngreen;
}

.invalidField {
    background-color: tomato;
}

Создайте производный от FieldCssClassProvider класс, который проверяет сообщения о проверке поля и применяет соответствующий допустимый или недопустимый стиль.

CustomFieldClassProvider.cs:

using Microsoft.AspNetCore.Components.Forms;

public class CustomFieldClassProvider : FieldCssClassProvider
{
    public override string GetFieldCssClass(EditContext editContext, 
        in FieldIdentifier fieldIdentifier)
    {
        var isValid = editContext.IsValid(fieldIdentifier);

        return isValid ? "validField" : "invalidField";
    }
}
using Microsoft.AspNetCore.Components.Forms;

public class CustomFieldClassProvider : FieldCssClassProvider
{
    public override string GetFieldCssClass(EditContext editContext, 
        in FieldIdentifier fieldIdentifier)
    {
        var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();

        return isValid ? "validField" : "invalidField";
    }
}

Задайте класс CustomFieldClassProvider как поставщик класса CSS для полей в экземпляре формы EditContext с помощью SetFieldCssClassProvider.

Starship13.razor:

@page "/starship-13"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship13> Logger

<EditForm EditContext="editContext" OnValidSubmit="Submit" FormName="Starship13">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <div>
        <label>
            Identifier: 
            <InputText @bind-Value="Model!.Id" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

@code {
    private EditContext? editContext;

    [SupplyParameterFromForm]
    private Starship? Model { get; set; }

    protected override void OnInitialized()
    {
        Model ??= new();
        editContext = new(Model);
        editContext.SetFieldCssClassProvider(new CustomFieldClassProvider());
    }

    private void Submit() => Logger.LogInformation("Submit: Processing form");

    public class Starship
    {
        [Required]
        [StringLength(10, ErrorMessage = "Id is too long.")]
        public string? Id { get; set; }
    }
}
@page "/starship-13"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship13> Logger

<EditForm EditContext="editContext" OnValidSubmit="Submit" FormName="Starship13">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <div>
        <label>
            Identifier: 
            <InputText @bind-Value="Model!.Id" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

@code {
    private EditContext? editContext;

    [SupplyParameterFromForm]
    private Starship? Model { get; set; }

    protected override void OnInitialized()
    {
        Model ??= new();
        editContext = new(Model);
        editContext.SetFieldCssClassProvider(new CustomFieldClassProvider());
    }

    private void Submit() => Logger.LogInformation("Submit: Processing form");

    public class Starship
    {
        [Required]
        [StringLength(10, ErrorMessage = "Id is too long.")]
        public string? Id { get; set; }
    }
}
@page "/starship-13"
@using System.ComponentModel.DataAnnotations
@inject ILogger<Starship13> Logger

<EditForm EditContext="editContext" OnValidSubmit="Submit">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <InputText @bind-Value="Model!.Id" />
    <button type="submit">Submit</button>
</EditForm>

@code {
    private EditContext? editContext;

    public Starship? Model { get; set; }

    protected override void OnInitialized()
    {
        Model ??= new();
        editContext = new(Model);
        editContext.SetFieldCssClassProvider(new CustomFieldClassProvider());
    }

    private void Submit()
    {
        Logger.LogInformation("Submit called: Processing the form");
    }

    public class Starship
    {
        [Required]
        [StringLength(10, ErrorMessage = "Id is too long.")]
        public string? Id { get; set; }
    }
}

В предыдущем примере проверяется допустимость всех полей формы и применяется стиль к каждому такому полю. Если форма должна применять пользовательские стили только к подмножеству полей, реализуйте в CustomFieldClassProvider условное применение стилей. В следующем примере Name стиль применяется только к полю CustomFieldClassProvider2. Для всех полей, имена которых не соответствуют Name, возвращается string.Empty, а стиль не применяется. Используя отражение, поле соответствует свойству или имени поля элемента модели, а не id к сущности HTML.

CustomFieldClassProvider2.cs:

using Microsoft.AspNetCore.Components.Forms;

public class CustomFieldClassProvider2 : FieldCssClassProvider
{
    public override string GetFieldCssClass(EditContext editContext,
        in FieldIdentifier fieldIdentifier)
    {
        if (fieldIdentifier.FieldName == "Name")
        {
            var isValid = editContext.IsValid(fieldIdentifier);

            return isValid ? "validField" : "invalidField";
        }

        return string.Empty;
    }
}
using Microsoft.AspNetCore.Components.Forms;

public class CustomFieldClassProvider2 : FieldCssClassProvider
{
    public override string GetFieldCssClass(EditContext editContext,
        in FieldIdentifier fieldIdentifier)
    {
        if (fieldIdentifier.FieldName == "Name")
        {
            var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();

            return isValid ? "validField" : "invalidField";
        }

        return string.Empty;
    }
}

Примечание.

Сопоставление имени поля в предыдущем примере учитывает регистр, поэтому член свойства модели, назначенный "Name" должен соответствовать условной проверке "Name":

  • Правильно соответствует: fieldId.FieldName == "Name"
  • Не удается сопоставить: fieldId.FieldName == "name"
  • Не удается сопоставить: fieldId.FieldName == "NAME"
  • Не удается сопоставить: fieldId.FieldName == "nAmE"

Добавьте дополнительное свойство в Model, например:

[StringLength(10, ErrorMessage = "Description is too long.")]
public string? Description { get; set; } 

Добавьте Description в форму компонента CustomValidationForm:

<InputText @bind-Value="Model!.Description" />

Обновите экземпляр EditContext в методе OnInitialized компонента, чтобы использовать нового поставщика класса CSS для полей:

editContext?.SetFieldCssClassProvider(new CustomFieldClassProvider2());

Так как класс проверки CSS не применяется к Description полю, он не стилем. Однако проверка полей выполняется обычным образом. Если будет использоваться более 10 символов, сводка проверки укажет на ошибку:

Description is too long (Слишком длинное описание.)

В следующем примере :

  • К полю Name применяется пользовательский стиль CSS.

  • Любые другие поля применяют логику, похожую на логику Blazor по умолчанию, и используют стили проверки CSS по умолчанию Blazor для полей, modified с valid или invalid. Обратите внимание, что стили по умолчанию не нужно добавлять в таблицу стилей приложения, если приложение основано на шаблоне проекта Blazor. Если приложение не основано на шаблоне проекта Blazor, стили по умолчанию можно добавить в таблицу стилей приложения.

    .valid.modified:not([type=checkbox]) {
        outline: 1px solid #26b050;
    }
    
    .invalid {
        outline: 1px solid red;
    }
    

CustomFieldClassProvider3.cs:

using Microsoft.AspNetCore.Components.Forms;

public class CustomFieldClassProvider3 : FieldCssClassProvider
{
    public override string GetFieldCssClass(EditContext editContext,
        in FieldIdentifier fieldIdentifier)
    {
        var isValid = editContext.IsValid(fieldIdentifier);

        if (fieldIdentifier.FieldName == "Name")
        {
            return isValid ? "validField" : "invalidField";
        }
        else
        {
            if (editContext.IsModified(fieldIdentifier))
            {
                return isValid ? "modified valid" : "modified invalid";
            }
            else
            {
                return isValid ? "valid" : "invalid";
            }
        }
    }
}
using Microsoft.AspNetCore.Components.Forms;

public class CustomFieldClassProvider3 : FieldCssClassProvider
{
    public override string GetFieldCssClass(EditContext editContext,
        in FieldIdentifier fieldIdentifier)
    {
        var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();

        if (fieldIdentifier.FieldName == "Name")
        {
            return isValid ? "validField" : "invalidField";
        }
        else
        {
            if (editContext.IsModified(fieldIdentifier))
            {
                return isValid ? "modified valid" : "modified invalid";
            }
            else
            {
                return isValid ? "valid" : "invalid";
            }
        }
    }
}

Обновите экземпляр EditContext в методе компонента OnInitialized, чтобы использовать предыдущий поставщик класса CSS для полей:

editContext.SetFieldCssClassProvider(new CustomFieldClassProvider3());

Использование среды CustomFieldClassProvider3:

  • В поле Name используются пользовательские стили проверки CSS приложения.
  • В поле Description используется логика, аналогичная логике Blazor, и стили проверки CSS по умолчанию для полей Blazor.

Проверка уровня класса с помощью IValidatableObject

Проверка уровня класса с помощью IValidatableObject (документации по API) поддерживается для Blazor моделей форм. IValidatableObject проверка выполняется только при отправке формы и только в том случае, если все остальные проверки выполнены успешно.

Пакет проверки заметок к данным в Blazor

Microsoft.AspNetCore.Components.DataAnnotations.Validation — это пакет, который выполняет проверку пропусков заполнения с помощью компонента DataAnnotationsValidator. В настоящее время пакет является экспериментальным.

Предупреждение

У пакета Microsoft.AspNetCore.Components.DataAnnotations.Validation есть последняя версия релиз-кандидата на сайте NuGet.org. Пока продолжайте использовать экспериментальный пакет релиз-кандидата. Экспериментальные функции предоставляются в целях изучения их целесообразности и могут не входить в состав стабильной версии. Дополнительные обновления см. в репозитории GitHub для объявлений,dotnet/aspnetcore репозитория GitHub или в этом разделе.

Атрибут [CompareProperty]

Он CompareAttribute не работает хорошо с компонентом DataAnnotationsValidator , так как DataAnnotationsValidator результат проверки не связывается с определенным элементом. Это может привести к несогласованному поведению при проверке на уровне полей и при проверке всей модели при отправке. Экспериментальный пакет Microsoft.AspNetCore.Components.DataAnnotations.Validation содержит дополнительный атрибут проверки ComparePropertyAttribute, который обходит эти ограничения. В приложении Blazor объект [CompareProperty] является непосредственной заменой атрибута [Compare].

Вложенные модели, типы коллекций и сложные типы

Blazor обеспечивает поддержку проверки входных данных формы с помощью заметок к данным со встроенным DataAnnotationsValidator. Однако DataAnnotationsValidator проверяет только свойства верхнего уровня модели, привязанной к форме, которые не являются свойствами типа коллекции или сложного типа.

Чтобы проверить весь граф объектов привязанной модели, включая свойства типа коллекции и сложного типа, используйте ObjectGraphDataAnnotationsValidator, предоставляемый экспериментальным пакетом Microsoft.AspNetCore.Components.DataAnnotations.Validation:

<EditForm ...>
    <ObjectGraphDataAnnotationsValidator />
    ...
</EditForm>

Делайте заметки для свойств модели с помощью [ValidateComplexType]. В следующих классах модели класс ShipDescription содержит дополнительные заметки к данным для проверки, когда модель привязана к форме:

Starship.cs:

using System;
using System.ComponentModel.DataAnnotations;

public class Starship
{
    ...

    [ValidateComplexType]
    public ShipDescription ShipDescription { get; set; } = new();

    ...
}

ShipDescription.cs:

using System;
using System.ComponentModel.DataAnnotations;

public class ShipDescription
{
    [Required]
    [StringLength(40, ErrorMessage = "Description too long (40 char).")]
    public string? ShortDescription { get; set; }

    [Required]
    [StringLength(240, ErrorMessage = "Description too long (240 char).")]
    public string? LongDescription { get; set; }
}

Предоставление кнопки "Отправить" на основе проверки формы

Чтобы включить или отключить кнопку "Отправить" на основе проверки формы, в примере ниже реализовано следующее:

  • Использует сокращенную версию более ранней Starfleet Starship Database формы (компонента) раздела формы "Пример" статьи "Входные компоненты"Starship3, которая принимает значение только для идентификатора корабля. Другие Starship свойства получают допустимые значения по умолчанию при создании экземпляра Starship типа.
  • Используется EditContext формы, чтобы назначить модель при инициализации компонента.
  • Форма проверяется в обратном вызове OnFieldChanged контекста, чтобы включить и отключить кнопку "Отправить".
  • Реализуется IDisposable и отменяется подписка обработчика событий в методе Dispose. Дополнительные сведения см. в статье Жизненный цикл компонентов Razor ASP.NET Core.

Примечание.

При назначении элементу EditForm.EditContext не следует также назначать EditForm.Model для EditForm.

Starship14.razor:

@page "/starship-14"
@implements IDisposable
@inject ILogger<Starship14> Logger

<EditForm EditContext="editContext" OnValidSubmit="Submit" FormName="Starship14">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <div>
        <label>
            Identifier:
            <InputText @bind-Value="Model!.Id" />
        </label>
    </div>
    <div>
        <button type="submit" disabled="@formInvalid">Submit</button>
    </div>
</EditForm>

@code {
    private bool formInvalid = false;
    private EditContext? editContext;

    [SupplyParameterFromForm]
    private Starship? Model { get; set; }

    protected override void OnInitialized()
    {
        Model ??=
            new()
                {
                    Id = "NCC-1701",
                    Classification = "Exploration",
                    MaximumAccommodation = 150,
                    IsValidatedDesign = true,
                    ProductionDate = new DateTime(2245, 4, 11)
                };
        editContext = new(Model);
        editContext.OnFieldChanged += HandleFieldChanged;
    }

    private void HandleFieldChanged(object? sender, FieldChangedEventArgs e)
    {
        if (editContext is not null)
        {
            formInvalid = !editContext.Validate();
            StateHasChanged();
        }
    }

    private void Submit() => Logger.LogInformation("Submit: Processing form");

    public void Dispose()
    {
        if (editContext is not null)
        {
            editContext.OnFieldChanged -= HandleFieldChanged;
        }
    }
}
@page "/starship-14"
@implements IDisposable
@inject ILogger<Starship14> Logger

<EditForm EditContext="editContext" OnValidSubmit="Submit" FormName="Starship14">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <div>
        <label>
            Identifier:
            <InputText @bind-Value="Model!.Id" />
        </label>
    </div>
    <div>
        <button type="submit" disabled="@formInvalid">Submit</button>
    </div>
</EditForm>

@code {
    private bool formInvalid = false;
    private EditContext? editContext;

    [SupplyParameterFromForm]
    private Starship? Model { get; set; }

    protected override void OnInitialized()
    {
        Model ??=
            new()
                {
                    Id = "NCC-1701",
                    Classification = "Exploration",
                    MaximumAccommodation = 150,
                    IsValidatedDesign = true,
                    ProductionDate = new DateTime(2245, 4, 11)
                };
        editContext = new(Model);
        editContext.OnFieldChanged += HandleFieldChanged;
    }

    private void HandleFieldChanged(object? sender, FieldChangedEventArgs e)
    {
        if (editContext is not null)
        {
            formInvalid = !editContext.Validate();
            StateHasChanged();
        }
    }

    private void Submit() => Logger.LogInformation("Submit: Processing form");

    public void Dispose()
    {
        if (editContext is not null)
        {
            editContext.OnFieldChanged -= HandleFieldChanged;
        }
    }
}
@page "/starship-14"
@implements IDisposable
@inject ILogger<Starship14> Logger

<EditForm EditContext="editContext" OnValidSubmit="Submit">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <div>
        <label>
            Identifier: 
            <InputText @bind-Value="Model!.Id" />
        </label>
    </div>
    <div>
        <button type="submit" disabled="@formInvalid">Submit</button>
    </div>
</EditForm>

@code {
    private bool formInvalid = false;
    private EditContext? editContext;

    private Starship? Model { get; set; }

    protected override void OnInitialized()
    {
        Model ??=
            new()
            {
                Id = "NCC-1701",
                Classification = "Exploration",
                MaximumAccommodation = 150,
                IsValidatedDesign = true,
                ProductionDate = new DateTime(2245, 4, 11)
            };
        editContext = new(Model);
        editContext.OnFieldChanged += HandleFieldChanged;
    }

    private void HandleFieldChanged(object? sender, FieldChangedEventArgs e)
    {
        if (editContext is not null)
        {
            formInvalid = !editContext.Validate();
            StateHasChanged();
        }
    }

    private void Submit()
    {
        Logger.LogInformation("Submit called: Processing the form");
    }

    public void Dispose()
    {
        if (editContext is not null)
        {
            editContext.OnFieldChanged -= HandleFieldChanged;
        }
    }
}

Если форма не была предварительно загружена с допустимыми значениями и вы хотите отключить кнопку Submit при загрузке формы, задайте для параметра formInvalid значение true.

Побочным эффектом предыдущего подхода является то, что сводка проверки (компонент ValidationSummary) заполняется недопустимыми полями после того, как пользователь взаимодействует с одним полем. Решить эту ситуацию можно одним из приведенных ниже способов.

  • Не используйте компонент ValidationSummary в форме.
  • Сделайте компонент ValidationSummary видимым при нажатии кнопки "Отправить" (например, в методе Submit).
<EditForm ... EditContext="editContext" OnValidSubmit="Submit" ...>
    <DataAnnotationsValidator />
    <ValidationSummary style="@displaySummary" />

    ...

    <button type="submit" disabled="@formInvalid">Submit</button>
</EditForm>

@code {
    private string displaySummary = "display:none";

    ...

    private void Submit()
    {
        displaySummary = "display:block";
    }
}