Partilhar via


Validação de formulários do ASP.NET Core Blazor

Observação

Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 9 deste artigo.

Aviso

Esta versão do ASP.NET Core não tem mais suporte. Para obter mais informações, consulte a Política de Suporte do .NET e do .NET Core. Para a versão atual, consulte a versão .NET 9 deste artigo.

Importante

Essas informações relacionam-se ao produto de pré-lançamento, que poderá ser substancialmente modificado antes do lançamento comercial. A Microsoft não oferece nenhuma garantia, explícita ou implícita, quanto às informações fornecidas aqui.

Para a versão atual, consulte a versão .NET 9 deste artigo.

Este artigo explica como usar a validação em formulários Blazor.

Validação de formulário

Em cenários básicos de validação de formulário, uma instância EditForm pode usar instâncias EditContext e ValidationMessageStore declaradas para validar campos de formulário. Um manipulador para o evento OnValidationRequested do EditContext executa lógica de validação personalizada. O resultado do manipulador atualiza a instância ValidationMessageStore.

A validação básica de formulário é útil nos casos em que o modelo do formulário é definido dentro do componente que hospeda o formulário, seja como membros diretamente no componente, seja em uma subclasse. O uso de um componente validador é recomendável quando uma classe de modelo independente é usada em vários componentes.

Em Blazor Web Apps, a validação do lado do cliente requer um circuito BlazorSignalR ativo. A validação do lado do cliente não está disponível para formulários em componentes que adotaram a renderização estática do lado do servidor (SSR estática). Os formulários que adotam SSR estático são validados no servidor depois que o formulário é enviado.

No componente a seguir, o método de manipulador HandleValidationRequested limpa todas as mensagens de validação existentes chamando ValidationMessageStore.Clear antes de validar o formulário.

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;
        }
    }
}

Componente Validador de Anotações de Dados e validação personalizada

O componente DataAnnotationsValidator anexa a validação de anotações de dados a um EditContext em cascata. A habilitação da validação de anotações de dados requer o componente DataAnnotationsValidator. Para usar um sistema de validação diferente das anotações de dados, use uma implementação personalizada em vez do componente DataAnnotationsValidator. As implementações de estrutura para DataAnnotationsValidator estão disponíveis para inspeção na fonte de referência:

Observação

Os links de documentação para a fonte de referência do .NET geralmente carregam o branch padrão do repositório, que representa o desenvolvimento atual da próxima versão do .NET. Para selecionar uma marca para uma versão específica, use a lista suspensa para Alternar branches ou marcas. Para saber mais, confira Como selecionar uma marca de versão do código-fonte do ASP.NET Core (dotnet/AspNetCore.Docs #26205).

O Blazor executa dois tipos de validação:

  • A Validação de campo é executada quando o usuário sai de um campo com a tecla Tab. Durante a validação de campo, o componente DataAnnotationsValidator associa todos os resultados de validação relatados com o campo.
  • A validação do modelo é executada quando o usuário envia o formulário. Durante a validação do modelo, o componente DataAnnotationsValidator tenta determinar o campo com base no nome do membro que o resultado da validação relata. Os resultados de validação que não estão associados a um membro individual são associados ao modelo em vez de a um campo.

Componentes validador

Os componentes validadores dão suporte à validação de formulário com o gerenciamento de um ValidationMessageStore para um EditContext de um formulário.

A estruturaBlazor fornece o componente DataAnnotationsValidator para anexar o suporte de validação a formulários com base em atributos de validação (anotações de dados). Você pode criar componentes validadores personalizados para processar mensagens de validação para diferentes formulários na mesma página ou no mesmo formulário em diferentes etapas de processamento de formulário (por exemplo, validação de cliente seguida de validação de servidor). O exemplo de componente do validador mostrado nesta seção, CustomValidation, é usado nas seções abaixo deste artigo:

Dos validadores internos de anotação de dados, apenas o atributo de validação [Remote] não é compatível no Blazor.

Observação

Os atributos de validação de anotação de dados personalizados podem ser usados no lugar de componentes de validador personalizados em muitos casos. Os atributos personalizados aplicados ao modelo do formulário são ativados com o uso do componente DataAnnotationsValidator. Quando usado com a validação do lado do servidor, é necessário que todos os atributos personalizados aplicados ao modelo possam ser executados no servidor. Para obter mais informações, consulte a seção Atributos de validação personalizados.

Crie um componente de validador de ComponentBase:

  • O formulário EditContext é um parâmetro em cascata do componente.
  • Quando o componente validador é inicializado, um novo ValidationMessageStore é criado para manter uma lista atual de erros de formulário.
  • O repositório de mensagens recebe erros quando o código do desenvolvedor no componente do formulário chama o método DisplayErrors. Os erros são transmitidos ao método DisplayErrors em um Dictionary<string, List<string>>. No dicionário, a chave é o nome do campo de formulário que tem um ou mais erros. O valor é a lista de erros.
  • As mensagens são desmarcadas quando uma das seguintes mensagens ocorre:
    • A validação é solicitada no EditContext quando o evento OnValidationRequested é gerado. Todos os erros são limpos.
    • Um campo é alterado no formulário quando o evento OnFieldChanged é acionado. Somente os erros do campo são limpos.
    • O método ClearErrors é chamado pelo código do desenvolvedor. Todos os erros são limpos.

Atualize o namespace na classe a seguir para corresponder ao namespace do aplicativo.

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();
    }
}

Importante

A especificação de um namespace é obrigatória quando derivado de ComponentBase. A não especificação do namespace resultará em um erro de build:

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

O espaço reservado {CLASS NAME} é o nome da classe do componente. O exemplo de validador personalizado nesta seção especifica o namespace de exemplo BlazorSample.

Observação

Expressões lambda anônimas são manipuladores de eventos registrados para OnValidationRequested e OnFieldChanged no exemplo anterior. Não é necessário implementar IDisposable e cancelar a assinatura dos delegados do evento nesse cenário. Para saber mais, consulte Ciclo de vida de renderização de Razor no ASP.NET Core.

Validação de lógica de negócios com um componente validador

Para validação geral da lógica de negócios, use um componente validador que recebe erros de formulário em um dicionário.

A validação básica é útil nos casos em que o modelo do formulário é definido dentro do componente que hospeda o formulário, seja como membros diretamente no componente, seja em uma subclasse. O uso de um componente validador é recomendável quando uma classe de modelo independente é usada em vários componentes.

No exemplo a seguir:

  • Uma versão reduzida do formulário Starfleet Starship Database (componente Starship3) da seção Formulário de exemplo do artigo Componentes de entrada é usada com aceitação apenas da classificação e da descrição da nave estelar. A validação de anotação de dados não é disparada no envio do formulário porque o componente DataAnnotationsValidator não está incluído no formulário.
  • O componente CustomValidation da seção componentes validadores deste artigo é usado.
  • A validação exigirá um valor para a descrição do navio (Description) se o usuário selecionar a classificação de navio "Defense" (Classification).

Quando as mensagens de validação são definidas no componente, elas são adicionadas ao ValidationMessageStore do validador e mostradas no resumo de validação do 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");
        }
    }
}

Observação

Como alternativa ao uso de componentes validadores, os atributos de validação de anotação de dados podem ser usados. Os atributos personalizados aplicados ao modelo do formulário são ativados com o uso do componente DataAnnotationsValidator. Quando usados com a validação do lado do servidor, é necessário que os atributos possam ser executados no servidor. Para obter mais informações, consulte a seção Atributos de validação personalizados.

Validação de servidor com um componente validador

Esta seção se concentra em cenários Blazor Web App, mas a abordagem para qualquer tipo de aplicativo que usa a validação de servidor com a API Web adota a mesma abordagem geral.

Esta seção se concentra em cenários Blazor WebAssembly hospedados, mas a abordagem para qualquer tipo de aplicativo que usa a validação de servidor com a API Web adota a mesma abordagem geral.

Há suporte à validação de servidor, além da validação do lado do cliente:

  • Processe a validação do cliente no formulário com o componente DataAnnotationsValidator.
  • Quando o formulário passar pela validação do cliente (OnValidSubmit é chamado), envie o EditContext.Model a uma API de servidor de back-end para processamento de formulário.
  • Processe a validação do modelo no servidor.
  • A API do servidor inclui a validação de anotações de dados da estrutura interna e a lógica de validação personalizada fornecida pelo desenvolvedor. Se a validação for aprovada no servidor, processe o formulário e envie de volta um código de status de êxito (200 - OK). Se a validação falhar, retorne um código de status de falha (400 - Bad Request) e os erros de validação de campo.
  • Desabilite o formulário com êxito ou exiba os erros.

A validação básica é útil nos casos em que o modelo do formulário é definido dentro do componente que hospeda o formulário, seja como membros diretamente no componente, seja em uma subclasse. O uso de um componente validador é recomendável quando uma classe de modelo independente é usada em vários componentes.

O exemplo abaixo baseia-se em:

Coloque o modelo Starship (Starship.cs) em um projeto de biblioteca de classes compartilhada para que os projetos de cliente e servidor possam usar o modelo. Adicione ou atualize o namespace para corresponder ao namespace do aplicativo compartilhado (por exemplo, namespace BlazorSample.Shared). Como o modelo requer anotações de dados, confirme se a biblioteca de classes compartilhada usa a estrutura compartilhada ou adicione o pacote System.ComponentModel.Annotations ao projeto compartilhado.

Observação

Para obter diretrizes sobre como adicionar pacotes a aplicativos .NET, consulte os artigos em Instalar e gerenciar pacotes no Fluxo de trabalho de consumo de pacotes (documentação do NuGet). Confirme as versões corretas de pacote em NuGet.org.

No projeto principal do Blazor Web App, adicione um controlador para processar solicitações de validação da nave estelar e retornar mensagens de validação com falha. Atualize os namespaces na última instrução using do projeto da biblioteca de classes compartilhada e o namespace para a classe do controlador. Além da validação das anotações de dados do cliente e do servidor, o controlador valida se um valor é fornecido para a descrição de envio (Description ) caso o usuário selecione a classificação de envio Defense (Classification ).

Coloque o modelo Starship (Starship.cs) no projeto Shared da solução para que os aplicativos cliente e servidor possam usar o modelo. Adicione ou atualize o namespace para corresponder ao namespace do aplicativo compartilhado (por exemplo, namespace BlazorSample.Shared). Como o modelo requer anotações de dados, adicione o pacote System.ComponentModel.Annotations ao projeto Shared.

Observação

Para obter diretrizes sobre como adicionar pacotes a aplicativos .NET, consulte os artigos em Instalar e gerenciar pacotes no Fluxo de trabalho de consumo de pacotes (documentação do NuGet). Confirme as versões corretas de pacote em NuGet.org.

No projeto Server, adicione um controlador para processar solicitações de validação de starship e retornar mensagens de validação com falha. Atualize os namespaces na última instrução using do projeto Shared e o namespace da classe do controlador. Além da validação das anotações de dados do cliente e do servidor, o controlador valida se um valor é fornecido para a descrição de envio (Description ) caso o usuário selecione a classificação de envio Defense (Classification ).

A validação para a classificação de envio Defense ocorre apenas no servidor no controlador porque o formulário seguinte não executa o mesmo cliente de validação quando o formulário é enviado para o servidor. A validação do servidor sem validação do cliente é comum em aplicativos que exigem a validação da lógica de negócios privada da entrada do usuário no servidor. Por exemplo, informações privadas de dados armazenados para um usuário podem ser exigidas para validar a entrada do usuário. Dados privados, obviamente, não podem ser enviados ao cliente para validação do cliente.

Observação

O controlador StarshipValidation nesta seção usa o Microsoft Identity 2.0. A API Web aceita apenas tokens para usuários que têm o escopo "API.Access" para essa API. Será necessário fazer uma personalização adicional se o nome do escopo da API for diferente de API.Access.

Para obter mais informações sobre segurança, confira:

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);
    }
}

Confirme ou atualize o namespace do controlador anterior (BlazorSample.Server.Controllers) para corresponder ao namespace dos controladores do aplicativo.

Quando ocorre um erro de validação de associação de modelo no servidor, um ApiController (ApiControllerAttribute) normalmente retorna uma resposta de solicitação inválida padrão com um ValidationProblemDetails. A resposta contém mais dados do que apenas os erros de validação, conforme mostrado no exemplo a seguir, quando todos os campos do formulário Starfleet Starship Database não são enviados e o formulário falha na validação:

{
  "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)."]
  }
}

Observação

Para demonstrar a resposta JSON anterior, você precisa desabilitar a validação do formulário do cliente para permitir o envio de formulário de campo vazio ou usar uma ferramenta para enviar uma solicitação diretamente à API do servidor, como Desenvolvedor do Navegador Firefox.

Se a API do servidor retornar a resposta JSON padrão anterior, o cliente poderá analisar a resposta no código do desenvolvedor para obter os filhos do nó errors para processamento de erro de validação de formulários. É inconveniente escrever código do desenvolvedor para analisar o arquivo. A análise manual do JSON requer a produção de um Dictionary<string, List<string>> dos erros após chamar ReadFromJsonAsync. Idealmente, a API do servidor só deve retornar os erros de validação, como mostra o seguinte exemplo:

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

Para modificar a resposta da API do servidor e fazer com que ela retorna apenas os erros de validação, altere o delegado invocado em ações anotadas com ApiControllerAttribute no arquivo Program. Para o ponto de extremidade da API (/StarshipValidation), retorne um BadRequestObjectResult com o ModelStateDictionary. Para outros pontos de extremidade de API, preserve o comportamento padrão retornando o resultado do objeto com um novo ValidationProblemDetails.

Adicione o namespace Microsoft.AspNetCore.Mvc à parte superior do arquivo Program no projeto principal do Blazor Web App:

using Microsoft.AspNetCore.Mvc;

No arquivo Program, adicione ou atualize o seguinte método de extensão AddControllersWithViews e adicione a seguinte chamada a 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));
            }
        };
    });

Se você estiver adicionando controladores ao projeto principal do Blazor Web App pela primeira vez, mapeie os pontos de extremidade do controlador quando colocar o código anterior que registra serviços de controladores. O exemplo a seguir usa rotas de controlador padrão:

app.MapDefaultControllerRoute();

Observação

O exemplo anterior registra explicitamente os serviços do controlador chamando AddControllersWithViews para atenuar automaticamente os ataques de XSRF/CSRF (solicitação intersite forjada). Se você usar apenas AddControllers, a anti-falsificação não será habilitada automaticamente.

Para obter mais informações sobre o roteamento do controlador e as respostas de erro de falha de validação, consulte os seguintes recursos:

No projeto .Client, adicione o componente CustomValidation mostrado na seção Componentes validadores. Atualize o namespace para corresponder ao aplicativo (por exemplo, namespace BlazorSample.Client).

No projeto .Client, o formulário Starfleet Starship Database é atualizado para mostrar erros de validação do servidor com a ajuda do componente CustomValidation. Quando a API do servidor retorna mensagens de validação, elas são adicionadas ao ValidationMessageStore do componente CustomValidation. Os erros estão disponíveis no EditContext do formulário para exibição pelo resumo de validação do formulário.

No componente a seguir, atualize o namespace do projeto compartilhado (@using BlazorSample.Shared) para o namespace do projeto compartilhado. Observe que o formulário requer autorização, ou seja, o usuário precisa ser conectado ao aplicativo para navegar até o formulário.

Adicione o namespace Microsoft.AspNetCore.Mvc à parte superior do arquivo Program no aplicativo Server:

using Microsoft.AspNetCore.Mvc;

No arquivo Program, localize o método de extensão AddControllersWithViews e adicione a seguinte chamada a 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));
            }
        };
    });

Observação

O exemplo anterior registra explicitamente os serviços do controlador chamando AddControllersWithViews para atenuar automaticamente os ataques de XSRF/CSRF (solicitação intersite forjada). Se você usar apenas AddControllers, a anti-falsificação não será habilitada automaticamente.

No projeto Client, adicione o componente CustomValidation mostrado na seção Componentes validadores. Atualize o namespace para corresponder ao aplicativo (por exemplo, namespace BlazorSample.Client).

No projeto Client, o formulário Starfleet Starship Database é atualizado para mostrar erros de validação do servidor com a ajuda do componente CustomValidation. Quando a API do servidor retorna mensagens de validação, elas são adicionadas ao ValidationMessageStore do componente CustomValidation. Os erros estão disponíveis no EditContext do formulário para exibição pelo resumo de validação do formulário.

No componente a seguir, atualize o namespace do projeto Shared (@using BlazorSample.Shared) para o namespace do projeto compartilhado. Observe que o formulário requer autorização, ou seja, o usuário precisa ser conectado ao aplicativo para navegar até o formulário.

Starship10.razor:

Observação

Os formulários baseados no EditForm habilitam automaticamente o suporte anti-falsificação. O controlador deve usar AddControllersWithViews para registrar serviços de controlador e habilitar automaticamente o suporte anti-falsificação para a API Web.

@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.";
        }
    }
}

O projeto .Client de um Blazor Web App também deve registrar um HttpClient para solicitações HTTP POST para um controlador da API Web de back-end. Confirme ou adicione o seguinte ao arquivo Program projeto .Client:

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

O exemplo anterior define o endereço base com builder.HostEnvironment.BaseAddress (IWebAssemblyHostEnvironment.BaseAddress), que obtém o endereço base do aplicativo e é normalmente derivado do valor href da tag <base> na página do host.

@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.";
        }
    }
}

Observação

Como alternativa ao uso de componentes validadores, os atributos de validação de anotação de dados podem ser usados. Os atributos personalizados aplicados ao modelo do formulário são ativados com o uso do componente DataAnnotationsValidator. Quando usados com a validação do lado do servidor, é necessário que os atributos possam ser executados no servidor. Para obter mais informações, consulte a seção Atributos de validação personalizados.

Observação

A abordagem de validação do servidor nessa seção é adequada para qualquer um dos exemplos de solução hospedados do Blazor WebAssembly nesse conjunto de documentação:

InputText com base no evento de entrada

Use o componente InputText para criar um componente personalizado que usa o evento oninput (input) em vez do evento onchange (change). O uso do evento input dispara validação de campo em cada pressionamento de tecla.

O componente CustomInputText a seguir herda o componente InputText estrutura e define a associação de eventos ao evento oninput (input).

CustomInputText.razor:

@inherits InputText

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

O componente CustomInputText pode ser usado em qualquer lugar em que InputText é usado. O componente a seguir usa o componente CustomInputText compartilhado.

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; }
    }
}

Componentes de Mensagem de Validação e Resumo de Validação

O componente ValidationSummary resume todas as mensagens de validação, que são semelhantes ao Auxiliar de marca de resumo de validação:

<ValidationSummary />

Mensagens de validação de saída para um modelo específico com o parâmetro Model:

<ValidationSummary Model="Model" />

O componente ValidationMessage<TValue> exibe mensagens de validação para um campo específico, que é semelhante ao Auxiliar de Marca de Mensagem de Validação. Especifique o campo para validação com o atributo For e uma expressão lambda que nomeia a propriedade do modelo:

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

Os componentes ValidationMessage<TValue> e ValidationSummary dão suporte a atributos arbitrários. Todo atributo que não corresponda a um parâmetro de componente é adicionado ao elemento <div> ou <ul> gerado.

Controle o estilo de mensagens de validação na folha de estilos do aplicativo (wwwroot/css/app.css ou wwwroot/css/site.css). A classe padrão validation-message define a cor do texto das mensagens de validação como vermelho:

.validation-message {
    color: red;
}

Determinar se um campo de formulário é válido

Use EditContext.IsValid para determinar se um campo é válido sem obter mensagens de validação.

Compatível, mas não recomendado:

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

Recomendado:

var isValid = editContext.IsValid(fieldIdentifier);

Atributos de validação personalizados

Para garantir que um resultado de validação esteja corretamente associado a um campo ao usar um atributo de validação personalizado, transmita o MemberName do contexto de validação ao criar o 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 });
    }
}

Injete serviços em atributos de validação personalizados por meio do ValidationContext. O exemplo a seguir demonstra um formulário de chef de salada que valida a entrada do usuário com DI (injeção de dependência).

A classe SaladChef indica a lista de ingredientes da nave estelar aprovados para uma salada do Ten Forward.

SaladChef.cs:

namespace BlazorSample;

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

Registre SaladChef no contêiner de DI do aplicativo no arquivo Program:

builder.Services.AddTransient<SaladChef>();

O método IsValid da classe SaladChefValidatorAttribute a seguir obtém o serviço SaladChef da DI para verificar a entrada do usuário.

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));
    }
}

O componente a seguir valida a entrada do usuário aplicando o SaladChefValidatorAttribute ([SaladChefValidator]) à cadeia de caracteres de ingrediente de salada (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);
}

Atributos de classe CSS de validação personalizada

Os atributos de classe CSS de validação personalizada são úteis na integração com estruturas CSS, por exemplo, o Bootstrap.

Para especificar atributos de classe CSS de validação personalizada, comece fornecendo estilos CSS para validação personalizada. No exemplo a seguir, estilos válidos (validField) e inválidos (invalidField) são especificados.

Adicione as seguintes classes CSS à folha de estilos do aplicativo:

.validField {
    border-color: lawngreen;
}

.invalidField {
    background-color: tomato;
}

Crie uma classe derivada dessa FieldCssClassProvider que verifica a validação de mensagens de campo e aplica o estilo válido ou inválido apropriado.

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";
    }
}

Defina a classe CustomFieldClassProvider como o Provedor de Classe CSS de Campo na instância EditContext do formulário com 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; }
    }
}

O exemplo anterior verifica a validade de todos os campos de formulário e aplica um estilo a cada campo. Se o formulário só deve aplicar estilos personalizados a um subconjunto dos campos, faça com que CustomFieldClassProvider aplique os estilos condicionalmente. O exemplo de CustomFieldClassProvider2 a seguir aplica apenas um estilo ao campo Name. Para todos os campos com nomes que não correspondem a Name, string.Empty é retornado e nenhum estilo é aplicado. Usando reflexão, o campo corresponderá à propriedade ou ao nome do campo do membro do modelo, não a um id atribuído à entidade 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;
    }
}

Observação

A correspondência do nome do campo no exemplo anterior diferencia maiúsculas de minúsculas, portanto, um membro de propriedade de modelo designado "Name" deve corresponder a uma verificação condicional em "Name":

  • Faz a correspondência corretamente: fieldId.FieldName == "Name"
  • Não faz a correspondência corretamente: fieldId.FieldName == "name"
  • Não faz a correspondência corretamente: fieldId.FieldName == "NAME"
  • Não faz a correspondência corretamente: fieldId.FieldName == "nAmE"

Adicione uma propriedade adicional a Model, por exemplo:

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

Adicione a Description ao formulário do componente CustomValidationForm:

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

Atualize a instância EditContext no método OnInitialized do componente para usar o novo Provedor de Classe CSS de campo:

editContext?.SetFieldCssClassProvider(new CustomFieldClassProvider2());

Como uma classe de validação CSS não é aplicada ao campo Description, ela não é estilizada. No entanto, a validação de campo é executada normalmente. Se mais de 10 caracteres forem fornecidos, o resumo da validação indicará o erro:

A descrição é muito longa.

No exemplo a seguir:

  • O estilo CSS personalizado é aplicado ao campo Name.

  • Todos os outros campos aplicam lógica semelhante à lógica padrão do Blazor e usam os estilos de validação de CSS de campo padrão do Blazor, modified com valid ou invalid. Observe que, para os estilos padrão, você não precisará adicioná-los à folha de estilos do aplicativo se o aplicativo for baseado em um modelo de projeto do Blazor. Para aplicativos que não se baseiam em um modelo de projeto do Blazor, os estilos padrão podem ser adicionados à folha de estilos do aplicativo:

    .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";
            }
        }
    }
}

Atualize a instância EditContext no método OnInitialized do componente para usar o Provedor de Classe CSS de campo anterior:

editContext.SetFieldCssClassProvider(new CustomFieldClassProvider3());

Usando CustomFieldClassProvider3:

  • O campo Name usa os estilos CSS de validação personalizados do aplicativo.
  • O campo Description usa uma lógica semelhante à lógica do Blazor e aos estilos de validação CSS de campo padrão do Blazor.

Validação em nível de classe com IValidatableObject

A validação de nível de classe com IValidatableObject (documentação da API) é suportada para modelos de formulário Blazor. IValidatableObject A validação só é executada quando o formulário é enviado e somente se todas as outras validações forem bem-sucedidas.

Pacote Blazor de validação de anotações de dados

O Microsoft.AspNetCore.Components.DataAnnotations.Validation é um pacote que preenche lacunas de experiência de validação usando o componente DataAnnotationsValidator. No momento, o pacote é experimental.

Aviso

O pacote Microsoft.AspNetCore.Components.DataAnnotations.Validation tem uma versão mais recente do release candidate em NuGet.org. Por enquanto, continue a usar o pacote da versão Release Candidate experimental. Os recursos experimentais são fornecidos com a finalidade de explorar a viabilidade do recurso e podem não ser fornecidos em uma versão estável. Fique de olho no Repositório de comunicados do GitHub, no dotnet/aspnetcoreRepositório do GitHub ou na seção deste tópico para obter mais atualizações.

Atributo [CompareProperty]

O CompareAttribute não funciona bem com o componente DataAnnotationsValidator porque o DataAnnotationsValidator não associa o resultado da validação a um membro específico. Isso pode resultar em um comportamento inconsistente entre a validação no nível do campo e quando todo o modelo é validado em um envio. O pacote experimental Microsoft.AspNetCore.Components.DataAnnotations.Validation apresenta um atributo de validação adicional, ComparePropertyAttribute, que resolve essas limitações. Em um Blazor aplicativo, [CompareProperty] é uma substituição direta do [Compare]atributo.

Modelos aninhados, tipos de coleção e tipos complexos

O Blazor dá suporte à validação da entrada do formulário usando anotações de dados com o DataAnnotationsValidator interno. No entanto, o DataAnnotationsValidator valida apenas as propriedades de nível superior do modelo associadas ao formulário que não são propriedades de tipo complexo ou coleção.

Para validar todo o grafo de objetos do modelo associado, incluindo propriedades de tipo complexo e coleção, use o ObjectGraphDataAnnotationsValidator fornecido pelo pacote experimental Microsoft.AspNetCore.Components.DataAnnotations.Validation:

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

Anote propriedades do modelo com [ValidateComplexType]. Nas classes de modelo abaixo, a classe ShipDescription contém anotações de dados adicionais para validar quando o modelo está associado ao formulário:

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; }
}

Habilitar o botão de envio com base na validação do formulário

Para habilitar e desabilitar o botão de envio com base na validação de formulário, o exemplo abaixo:

  • Usa uma versão reduzida do formulário Starfleet Starship Database anterior (componente Starship3) da seção Formulário de exemplo do artigo Componentes de entrada que aceita apenas um valor para a ID da nave. As outras propriedades Starship recebem valores padrão válidos quando uma instância do tipo Starship é criada.
  • Usa o formulário EditContext para atribuir o modelo quando o componente é inicializado.
  • Valida o formulário no retorno de chamada OnFieldChanged do contexto para habilitar e desabilitar o botão de envio.
  • Implementa IDisposable e cancela a assinatura do manipulador de eventos no método Dispose. Para saber mais, consulte Ciclo de vida de renderização de Razor no ASP.NET Core.

Observação

Ao atribuir ao EditForm.EditContext, não atribua também um EditForm.Model ao 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;
        }
    }
}

Se um formulário não for pré-carregado com valores válidos e você desejar desabilitar o botão Submit no carregamento do formulário, defina formInvalid como true.

Um efeito colateral da abordagem anterior é que um resumo de validação (componente ValidationSummary) é preenchido com campos inválidos depois que o usuário interage com algum campo. Resolva esse cenário de qualquer uma das seguintes maneiras:

  • Não use um componente ValidationSummary no formulário.
  • Torne o componente ValidationSummary visível quando o botão de envio estiver selecionado (por exemplo, em um método 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";
    }
}