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 10 deste artigo.

Advertência

Esta versão do ASP.NET Core não é mais suportada. Para obter mais informações, consulte a Política de suporte do .NET e .NET Core. 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 do formulário

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

A validação básica do 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 ou em uma subclasse. Recomenda-se o uso de um componente validador 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 de BlazorSignalR ativo. A validação do lado do cliente não está disponível para formulários em componentes que adotaram renderização estática do lado do servidor (SSR estático). Os formulários que adotam SSR estático são validados no servidor após o envio do formulário.

No componente a seguir, o método manipulador de 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 das anotações de dados a um EditContextem cascata. Habilitar a 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:

Para obter detalhes sobre o comportamento de validação, consulte a seção Comportamento de DataAnnotationsValidator validação .

Se precisares habilitar o suporte à validação de anotações de dados para um EditContext no código, chama EnableDataAnnotationsValidation com um IServiceProvider injetado (@inject IServiceProvider ServiceProvider) no EditContext. Para obter um exemplo avançado, consulte o componente NotifyPropertyChangedValidationComponent no Blazor do ASP.NET Core BasicTestApp framework (repositóriodotnet/aspnetcore GitHub). Em uma versão de produção do exemplo, substitua o argumento new TestServiceProvider() para o provedor de serviços por um IServiceProviderinjetado.

Observação

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

Blazor executa dois tipos de validação:

  • de validação de campo é executada quando o usuário sai de um campo. Durante a validação de campo, o componente DataAnnotationsValidator associa todos os resultados de validação relatados ao campo.
  • Validação do Modelo é realizada quando o utilizador submete o formulário. Durante a validação do modelo, o componente DataAnnotationsValidator tenta determinar o campo com base no nome do membro relatado pelo resultado da validação. Os resultados de validação que não estão associados a um membro individual são associados ao modelo e não a um campo.

Em cenários de validação personalizados:

Há duas abordagens gerais para obter a validação personalizada, que são descritas nas próximas duas seções deste artigo:

  • OnValidationRequestedde eventos : valide manualmente os campos de um formulário com validação de anotações de dados e código personalizado para verificações de campo quando a validação for solicitada por meio de um manipulador de eventos atribuído ao evento OnValidationRequested.
  • Componentes do validador: Um ou mais componentes do validador personalizado podem ser usados para processar a validação de diferentes formulários na mesma página ou no mesmo formulário em diferentes etapas do processamento do formulário (por exemplo, validação do cliente seguida da validação do servidor).

Validação manual usando o evento OnValidationRequested

Você pode validar manualmente um formulário com um manipulador de eventos personalizado atribuído ao evento EditContext.OnValidationRequested para gerenciar um ValidationMessageStore.

A estrutura Blazor fornece o componente DataAnnotationsValidator para anexar suporte de validação adicional a formulários com base em atributos de validação (anotações de dados).

Lembrando o exemplo de componente Starship8 anterior, o método HandleValidationRequested é atribuído a OnValidationRequested, onde você pode executar a validação manual em código C#. Algumas alterações demonstram a combinação da validação manual existente com a validação de anotações de dados por meio de um DataAnnotationsValidator e um atributo de validação aplicado ao modelo Holodeck.

Faça referência ao namespace System.ComponentModel.DataAnnotations nas diretivas Razor do componente na parte superior do arquivo de definição do componente:

@using System.ComponentModel.DataAnnotations

Adicione uma propriedade Id ao modelo Holodeck com um atributo validation para limitar o comprimento da cadeia de caracteres a seis caracteres:

[StringLength(6)]
public string? Id { get; set; }

Adicione um componente DataAnnotationsValidator (<DataAnnotationsValidator />) ao formulário. Normalmente, o componente é colocado imediatamente sob a tag <EditForm>, mas você pode colocá-lo em qualquer lugar no formato:

<DataAnnotationsValidator />

Altere o comportamento de envio do formulário na marca <EditForm> de OnSubmit para OnValidSubmit, o que garante que o formulário seja válido antes de executar o método de manipulador de eventos atribuído:

- OnSubmit="Submit"
+ OnValidSubmit="Submit"

No <EditForm>, adicione um campo para a propriedade Id:

<div>
    <label>
        <InputText @bind-Value="Model!.Id" />
        ID (6 characters max)
    </label>
    <ValidationMessage For="() => Model!.Id" />
</div>

Depois de fazer as alterações anteriores, o comportamento do formulário corresponde à seguinte especificação:

  • A validação de anotações de dados na propriedade Id não aciona uma falha de validação quando o campo Id simplesmente perde o foco. A validação é executada quando o usuário seleciona o botão Update.
  • Qualquer validação manual que você deseja executar no método HandleValidationRequested atribuído ao evento OnValidationRequested do formulário é executada quando o usuário seleciona o botão Update do formulário. No código existente do exemplo de componente Starship8, o usuário deve selecionar uma ou ambas as caixas de seleção para validar o formulário.
  • O formulário não processa o método Submit até que as anotações de dados e a validação manual sejam aprovadas.

Componentes do validador

Os componentes do validador suportam a validação de formulários gerenciando um ValidationMessageStore para a EditContextde um formulário.

A estrutura Blazor fornece o componente DataAnnotationsValidator para anexar suporte de validação a formulários baseados em atributos de validação (anotações de dados). Você pode criar componentes de validação personalizados para processar mensagens de validação para formulários diferentes 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 validador mostrado nesta seção, CustomValidation, é usado nas seguintes seções deste artigo:

Entre os validadores internos de anotação de dados , o único atributo de validação que não é suportado no [Remote]é o .

Observação

Atributos de validação de anotação de dados personalizados podem ser usados em vez 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 servidor, todos os atributos personalizados aplicados ao modelo devem ser executáveis no servidor. Para obter mais informações, consulte a seção atributos de validação personalizados.

Crie um componente validador a partir de ComponentBase:

  • O EditContext do formulário é 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 armazenamento de mensagens recebe erros quando o código do desenvolvedor no componente do formulário chama o método DisplayErrors. Os erros são passados para o 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 apagadas quando ocorre qualquer uma das seguintes situações:
    • A validação é solicitada no EditContext quando o evento OnValidationRequested é gerado. Todos os erros são eliminados.
    • Um campo muda no formulário quando o evento OnFieldChanged é gerado. Apenas os erros do campo são eliminados.
    • O método ClearErrors é chamado pelo código do desenvolvedor. Todos os erros são eliminados.

Atualize o namespace na classe a seguir para corresponder ao namespace do seu 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

Especificar um namespace é necessário ao derivar de ComponentBase. A falha ao especificar um namespace resulta em um erro de compilação:

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

O marcador de posição {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 inscrição dos delegados do evento nesse cenário. Para obter mais informações, consulte a eliminação de componentes do ASP.NET Core Razor.

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 ou em uma subclasse. O uso de um componente validador é recomendado quando uma classe de modelo independente é usada em vários componentes.

No exemplo a seguir:

  • É utilizada uma versão abreviada do formulário Starfleet Starship Database (componenteStarship3) da seção do formulário de exemplo do artigo Componentes de Entrada, que aceita apenas a classificação e descrição da nave estelar. A validação da anotação de dados não é acionada no envio do formulário porque o componente DataAnnotationsValidator não está incluído no formulário.
  • O componente CustomValidation da secção de componentes do Validador deste artigo é usado.
  • A validação requer um valor para a descrição do navio (Description) se o utilizador selecionar a classificação do 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),
                [ "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),
                [ "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 de validação , pode-se usar atributos de validação através de anotações de dados. 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 servidor, os atributos devem ser executáveis no servidor. Para obter mais informações, consulte a seção atributos de validação personalizados.

Validação do 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 validação de servidor com API da Web adota a mesma abordagem geral.

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

A validação do servidor é suportada, além da validação do cliente:

  • Realizar a validação do cliente no formulário utilizando o componente DataAnnotationsValidator.
  • Quando o formulário passar pela validação do cliente (OnValidSubmit é chamado), envie o EditContext.Model para uma API de servidor back-end para processamento de formulários.
  • Validação do modelo de processo 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 passar no servidor, processe o formulário e envie de volta um código de status de sucesso (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.
  • Desative o formulário em caso de ê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 ou em uma subclasse. O uso de um componente validador é recomendado quando uma classe de modelo independente é usada em vários componentes.

O exemplo a seguir é baseado em:

  • Um Blazor Web App com componentes Interativos de WebAssembly criados a partir do modelo de projeto Blazor Web App.
  • O modelo de Starship (Starship.cs) do formulário Exemplo de seção do artigo Componentes de entrada.
  • O componente CustomValidation mostrado na seção de componentes do Validador de.

Coloque o modelo de Starship (Starship.cs) em um projeto de biblioteca de classe compartilhada para que os projetos 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 orientação sobre como adicionar pacotes a aplicativos .NET, consulte os artigos em Instalar e gerenciar pacotes em Fluxo de trabalho de consumo de pacotes (documentação do NuGet). Confirme as versões corretas do pacote em NuGet.org.

No projeto principal do Blazor Web App, adicionar um controlador para processar solicitações de validação de nave estelar e retornar mensagens de falha de validação. Atualize os namespaces na última instrução using para o projeto de biblioteca de classe compartilhada e o namespace para a classe controller. Além da validação das anotações de dados do cliente e do servidor, o controlador valida que é fornecido um valor para a descrição do navio (Description) se o utilizador selecionar a classificação Defense do navio (Classification).

  • Uma solução Blazor WebAssemblyhospedada criada a partir do modelo de projeto Blazor WebAssembly. A abordagem é suportada para qualquer uma das soluções seguras hospedadas de Blazor descritas na documentação de segurança do hospedado Blazor WebAssembly.
  • O modelo de Starship (Starship.cs) do formulário Exemplo de seção do artigo Componentes de entrada.
  • O componente CustomValidation mostrado na seção de componentes do Validador de.

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 orientação sobre como adicionar pacotes a aplicativos .NET, consulte os artigos em Instalar e gerenciar pacotes em Fluxo de trabalho de consumo de pacotes (documentação do NuGet). Confirme as versões corretas do pacote em NuGet.org.

No projeto Server, adicione um controlador para processar solicitações de validação de nave estelar e retornar mensagens de validação com falha. Atualize os namespaces na última instrução using para o projeto Shared e o namespace para a classe controller. Além da validação das anotações de dados do cliente e do servidor, o controlador valida que é fornecido um valor para a descrição do navio (Description) se o utilizador selecionar a classificação Defense do navio (Classification).

A validação para a classificação de navios Defense ocorre apenas no servidor no controlador porque o formulário subsequente não executa a mesma validação do lado do cliente quando é enviado para o servidor. A validação de servidor sem validação de cliente é comum em aplicativos que exigem validação de 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 necessárias para validar a entrada do usuário. Os 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 da Web só aceita tokens para usuários que tenham o escopo "API.Access" para esta API. A personalização adicional será necessária se o nome do escopo da API for diferente de API.Access.

Para mais informações sobre segurança, consulte:

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 = [ "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);
    }
}
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 vinculação de modelo no servidor, um ApiController (ApiControllerAttribute) normalmente retorna uma resposta de solicitação incorreta padrão com um ValidationProblemDetails. A resposta contém mais dados do que apenas os erros de validação, como 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ê deve desativar a validação do cliente do formulário para permitir o envio de formulário de campo vazio ou usar uma ferramenta para enviar uma solicitação diretamente para a API do servidor, como Firefox Browser Developer.

Se a API do servidor retornar a resposta JSON padrão anterior, será possível que o cliente analise a resposta no código do desenvolvedor para obter os filhos do nó errors para processamento de erros de validação de formulários. É inconveniente escrever código de desenvolvedor para analisar o arquivo. Analisar o JSON manualmente exige o aparecimento de um número Dictionary<string, List<string>> de erros após a execução de ReadFromJsonAsync. Idealmente, a API do servidor deve retornar apenas os erros de validação, como mostra o exemplo a seguir:

{
  "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 para que ela retorne apenas os erros de validação, altere o delegado invocado em ações anotadas com ApiControllerAttribute no arquivo Program. Para o endpoint da API (/StarshipValidation), retorne um BadRequestObjectResult com o ModelStateDictionary. Para qualquer outro ponto de extremidade da API, mantenha o comportamento predefinido 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 para 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 estiver a adicionar controladores ao projeto principal do Blazor Web App pela primeira vez, mapeie os endpoints dos controladores ao inserir o código acima mencionado que regista serviços para 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 mitigar automaticamente os ataques de falsificação de solicitação entre sites (XSRF/CSRF). Se você apenas usar AddControllers, o antifalsificação não será ativado automaticamente.

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

No projeto .Client, adicione o componente CustomValidation mostrado na seção , na lista de componentes do Validator. Atualize o namespace para corresponder ao aplicativo (por exemplo, namespace BlazorSample.Client).

No projeto .Client, o formulário de 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 CustomValidationdo componente ValidationMessageStore . Os erros estão disponíveis na secção EditContext do formulário para exibição pelo sumário 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, portanto, o usuário deve estar conectado ao aplicativo para navegar até o formulário.

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

using Microsoft.AspNetCore.Mvc;

No arquivo Program, localize o método de extensão AddControllersWithViews e adicione a seguinte chamada para 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 mitigar automaticamente os ataques de falsificação de solicitação entre sites (XSRF/CSRF). Se você apenas usar AddControllers, o antifalsificação não será ativado automaticamente.

No projeto Client, adicione o componente CustomValidation mostrado na seção , na lista de componentes do Validator. Atualize o namespace para corresponder ao aplicativo (por exemplo, namespace BlazorSample.Client).

No projeto Client, o formulário de 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 CustomValidationdo componente ValidationMessageStore . Os erros estão disponíveis na secção EditContext do formulário para exibição pelo sumário 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, portanto, o usuário deve estar conectado ao aplicativo para navegar até o formulário.

Starship10.razor:

Observação

Os formulários baseados em EditForm ativam automaticamente o suporte antifalsificação . O controlador deve usar AddControllersWithViews para registrar os serviços do controlador e habilitar automaticamente o suporte antifalsificação para a API da 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
        {
            using 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 de API de back-end da web. Confirme ou adicione o seguinte ao ficheiro .Client do projeto Program.

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 para o aplicativo e normalmente é derivado do valor <base> da tag href na página de 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
        {
            using 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 um componente de validação de , 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 servidor, os atributos devem ser executáveis no servidor. Para obter mais informações, consulte a seção atributos de validação personalizados.

Observação

A abordagem de validação de servidor nesta seção é adequada para qualquer um dos exemplos de solução de Blazor WebAssembly hospedada neste 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 aciona a validação de campo em cada pressionamento de tecla.

O componente CustomInputText a seguir herda o componente InputText da estrutura e define a vinculaçã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 onde 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 Resumo de Validação e Mensagem de Validação

O componente ValidationSummary resume todas as mensagens de validação, que é semelhante ao Validation Summary Tag Helper:

<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 Validation Message Tag Helper. Especifique o campo para validação com o atributo For e uma expressão lambda nomeando a propriedade model:

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

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

Controle o estilo das mensagens de validação na folha de estilo do aplicativo (wwwroot/css/app.css ou wwwroot/css/site.css). A classe validation-message padrão 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.

Suportado, 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 , passe a 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.",
            [ validationContext.MemberName! ]);
    }
}
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! });
    }
}
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 saladas que valida a entrada do utilizador com injeção de dependência (DI).

A classe SaladChef indica a lista aprovada de ingredientes de bordo para uma salada Ten Forward.

SaladChef.cs:

namespace BlazorSample;

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

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

builder.Services.AddTransient<SaladChef>();

O método IsValid da classe SaladChefValidatorAttribute a seguir obtém o serviço de 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 do ingrediente da 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 ao integrar com estruturas CSS, como 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 de FieldCssClassProvider que verifica se há mensagens de validação 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 utilizando 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 estilos condicionalmente. O exemplo CustomFieldClassProvider2 a seguir aplica apenas um estilo ao campo Name. Para quaisquer campos com nomes que não correspondam Name, string.Empty é retornado e nenhum estilo é aplicado. Usando de reflexão, o campo é correspondido à 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 com o nome do campo no exemplo anterior diferencia maiúsculas de minúsculas, portanto, um membro da propriedade do modelo designado "Name" deve corresponder a uma verificação condicional em "Name":

  • ✔️ corresponde corretamente:fieldId.FieldName == "Name"
  • Falha ao corresponder:fieldId.FieldName == "name"
  • Falha ao corresponder:fieldId.FieldName == "NAME"
  • Falha ao corresponder:fieldId.FieldName == "nAmE"

Adicione uma propriedade adicional a Model, por exemplo:

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

Adicione o 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 Field CSS Class Provider:

editContext?.SetFieldCssClassProvider(new CustomFieldClassProvider2());

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

A descrição é muito longa.

No exemplo a seguir:

  • O estilo CSS personalizado é aplicado ao campo Name.

  • Quaisquer outros campos aplicam lógica semelhante à lógica padrão do Blazore usando estilos de validação CSS do campo padrão do Blazor, modified com valid ou invalid. Observe que, para os estilos padrão, você não precisa adicioná-los à folha de estilo do aplicativo se o aplicativo for baseado em um modelo de projeto Blazor. Para aplicativos não baseados em um modelo de projeto Blazor, os estilos padrão podem ser adicionados à folha de estilo 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 Field CSS Class Provider anterior:

editContext.SetFieldCssClassProvider(new CustomFieldClassProvider3());

Usando CustomFieldClassProvider3:

  • O campo Name usa os estilos CSS de validação personalizados do aplicativo.
  • O campo Description usa lógica semelhante à lógica do Blazore aos estilos de validação CSS do 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) é compatível com modelos de formulários Blazor. IValidatableObject validação só é executada quando o formulário é enviado e apenas se todas as outras validações forem concluídas com sucesso.

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

Observação

O Microsoft.AspNetCore.Components.DataAnnotations.Validation pacote não é mais recomendado para aplicativos destinados ao .NET 10 ou posterior. Para obter mais informações, consulte a seção Objetos aninhados, tipos de coleção e tipos complexos .

O Microsoft.AspNetCore.Components.DataAnnotations.Validation pacote preenche lacunas de experiência de validação usando o DataAnnotationsValidator componente. O pacote está atualmente experimental.

Advertência

O Microsoft.AspNetCore.Components.DataAnnotations.Validation pacote tem uma versão mais recente do release candidate em NuGet.org. Continue a usar o pacote candidato a liberação experimental neste momento. Os recursos experimentais são fornecidos com a finalidade de explorar a viabilidade do recurso e não podem ser enviados em uma versão estável. Assista ao repositório GitHub Anúncios, ao repositório GitHub dotnet/aspnetcoreou a esta seção de tópicos para obter mais atualizações.

[CompareProperty] atributo

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 de campo e quando todo o modelo é validado em um envio. O Microsoft.AspNetCore.Components.DataAnnotations.Validation pacote experimental introduz um atributo de validação adicional, ComparePropertyAttribute, que contorna essas limitações. Em um aplicativo Blazor, [CompareProperty] é um substituto direto para o atributo [Compare].

Objetos aninhados e tipos de coleção

BlazorA validação de formulário inclui suporte para validar propriedades de objetos aninhados e itens de coleção com o .DataAnnotationsValidator

Para criar um formulário validado, use um DataAnnotationsValidator componente dentro de um EditForm componente, como antes.

Para aderir ao recurso de validação de objetos aninhados e tipos de coleções:

  1. Chame o método de extensão AddValidation no ficheiro Program onde os serviços são registados.
  2. Declare os tipos de modelo de formulário em um arquivo de classe C#, não em um Razor componente (.razor).
  3. Anote o tipo de modelo de formulário raiz com o [ValidatableType] atributo.

Sem seguir as etapas anteriores, o comportamento de validação de formulário não inclui modelo aninhado e validação de tipo de coleção.

O exemplo a seguir demonstra pedidos de clientes com a validação de formulário aprimorada (detalhes omitidos para brevidade):

No Program.cs, chame AddValidation na coleção de serviços:

builder.Services.AddValidation();

Na classe a seguir Order , o [ValidatableType] atributo é necessário no tipo de modelo de nível superior. Os outros tipos são descobertos automaticamente. OrderItem e ShippingAddress não são mostrados para simplificar, mas a validação de tipos aninhados e de coleções funciona da mesma maneira nesses tipos, caso fossem apresentados.

Order.cs:

using System.ComponentModel.DataAnnotations;

[ValidatableType]
public class Order
{
    public Customer Customer { get; set; } = new();
    public List<OrderItem> OrderItems { get; set; } = [];
}

public class Customer
{
    [Required(ErrorMessage = "Name is required.")]
    public string? FullName { get; set; }

    [Required(ErrorMessage = "Email is required.")]
    public string? Email { get; set; }

    public ShippingAddress ShippingAddress { get; set; } = new();
}

No seguinte componente OrderPage, o DataAnnotationsValidator está presente no EditForm.

OrderPage.razor:

<EditForm Model="Model">
    <DataAnnotationsValidator />

    <h3>Customer Details</h3>
    <div class="mb-3">
        <label>
            Full Name
            <InputText @bind-Value="Model!.Customer.FullName" />
        </label>
        <ValidationMessage For="@(() => Model!.Customer.FullName)" />
    </div>

    // ... form continues ...
</EditForm>

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

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

O requisito para declarar os tipos de modelo fora dos componentes (Razor arquivos) é devido ao fato de que tanto a nova funcionalidade de validação quanto o próprio compilador .razor estão usando um gerador de código-fonte Razor. Atualmente, a saída de um gerador de fonte não pode ser usada como entrada para outro gerador de fonte.

Para orientações sobre a utilização de modelos de validação de uma montagem diferente, consulte Utilizar modelos de validação de uma secção de montagem diferente .

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

Observação

Para aplicativos destinados ao .NET 10 ou posterior, não recomendamos mais o uso do pacote Microsoft.AspNetCore.Components.DataAnnotations.Validation e da abordagem descritos nesta seção. Recomendamos o uso dos recursos de validação internos do DataAnnotationsValidator componente.

Blazor fornece suporte para validar a entrada de formulários usando anotações de dados incorporadas no DataAnnotationsValidator. No entanto, o DataAnnotationsValidator no .NET 9 ou anterior só valida propriedades de nível superior do modelo vinculado ao formulário que não são propriedades de tipo complexo ou de coleção.

Para validar o gráfico de objeto inteiro do modelo acoplado, incluindo propriedades de tipo complexo e de coleção, use o ObjectGraphDataAnnotationsValidator fornecido pelo pacote experimentalMicrosoft.AspNetCore.Components.DataAnnotations.Validation no .NET 9 ou anterior:

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

Anote as propriedades do modelo com [ValidateComplexType]. Nas seguintes classes de modelo, a classe ShipDescription contém anotações de dados adicionais para validar quando o modelo está vinculado 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; }
}

Usar modelos de validação de um assembly diferente

Para validação de modelo definido em um assembly diferente, como uma biblioteca ou o .Client projeto de um Blazor Web App:

  • Se a biblioteca for uma biblioteca de classes simples (não é baseada nos SDKs Microsoft.NET.Sdk.WebMicrosoft.NET.Sdk.Razor), adicione uma referência de pacote na biblioteca para o pacote NuGet Microsoft.Extensions.Validation. São necessários passos adicionais para bibliotecas de classes simples, que são descritos mais adiante nesta secção.
  • Crie um método na biblioteca ou no projeto .Client que receba uma instância IServiceCollection como argumento e invoque AddValidation sobre ela.
  • Na aplicação, chame tanto o método como AddValidation.

A abordagem anterior resulta na validação dos tipos de ambos os assemblies.

No exemplo a seguir, o método AddValidationForTypesInClient é criado para o projeto .Client de um Blazor Web App para validação usando tipos definidos no projeto .Client.

ServiceCollectionExtensions.cs (no projeto .Client):

namespace BlazorSample.Client.Extensions;

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddValidationForTypesInClient(
        this IServiceCollection collection)
    {
        return collection.AddValidation();
    }
}

No arquivo do projeto de servidor Program, adicione o namespace e chame o método de extensão do projeto para coleção de serviços .Client (AddValidationForTypesInClient) e AddValidation.

using BlazorSample.Client.Extensions;

...

builder.Services.AddValidationForTypesInClient();
builder.Services.AddValidation();

Os novos atributos do Microsoft.Extensions.Validation pacote (ValidatableTypeAttribute e SkipValidationAttribute) são publicados como experimentais no .NET 10. O pacote destina-se a fornecer uma nova infraestrutura partilhada para funcionalidades de validação entre frameworks, e a publicação de tipos experimentais proporciona maior flexibilidade para o design final da API pública para melhor suporte no consumo de frameworks.

Nas Blazor aplicações, os tipos são disponibilizados através de um atributo embutido gerado. Se um projeto de aplicação web que utiliza o Microsoft.NET.Sdk.Web SDK (<Project Sdk="Microsoft.NET.Sdk.Web">) ou um RCL que utiliza o Microsoft.NET.Sdk.Razor SDK (<Project Sdk="Microsoft.NET.Sdk.Razor">) contiver Razor componentes (.razor), a framework gera automaticamente um atributo interno dentro do projeto (Microsoft.Extensions.Validation.Embedded.ValidatableType, Microsoft.Extensions.Validation.Embedded.SkipValidation). Estes tipos são intercambiáveis com os atributos reais e não são marcados como experimentais. Na maioria dos casos, os programadores usam os [ValidatableType]/[SkipValidation] atributos nas suas classes sem se preocuparem com a fonte.

No entanto, a abordagem anterior não é viável em bibliotecas de classes simples que utilizam o Microsoft.NET.Sdk SDK (<Project Sdk="Microsoft.NET.Sdk">). A utilização dos tipos numa biblioteca de classes simples resulta num aviso de análise de código:

ASP0029: 'Microsoft.Extensions.Validation.ValidatableTypeAttribute' destina-se apenas a fins de avaliação e está sujeito a alterações ou remoção em futuras atualizações. Suprima este diagnóstico para prosseguir.

O aviso pode ser suprimido usando qualquer uma das seguintes abordagens:

  • Uma propriedade de <NoWarn> no ficheiro do projeto:

    <PropertyGroup>
      <NoWarn>$(NoWarn);ASP0029</NoWarn>
    </PropertyGroup>
    
  • Uma pragma diretiva onde o atributo é utilizado:

    #pragma warning disable ASP0029
    [Microsoft.Extensions.Validation.ValidatableType]
    #pragma warning restore ASP0029
    
  • Uma regra do ficheiro EditorConfig (.editorconfig):

    dotnet_diagnostic.ASP0029.severity = none
    

Caso suprimir o aviso não seja aceitável, crie manualmente o atributo incorporado na biblioteca que a Web e os SDKs geram automaticamente.

ValidatableTypeAttribute.cs:

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

Use o namespace exato (Microsoft.Extensions.Validation.Embedded) e o nome da classe (ValidatableTypeAttribute) para que o gerador de código-fonte de validação detete e use o tipo. Pode declarar uma instrução global using para o namespace, com uma instrução global using Microsoft.Extensions.Validation.Embedded; ou com um item <Using Include="Microsoft.Extensions.Validation.Embedded" /> no ficheiro de projeto da biblioteca.

Seja qual for a abordagem adotada, indique a presença da solução alternativa para uma futura atualização do seu código. Estão previstas atualizações do framework para facilitar a adoção de tipos de validação em bibliotecas de classes simples para o .NET 11 (novembro de 2026).

Ativar o botão Enviar 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 seguinte exemplo:

  • Usa uma versão abreviada do formulário de Starfleet Starship Database anterior (componenteStarship3) da seção formulário Exemplo de do artigo Componentes de entrada que só aceita um valor para o Id do navio. As outras propriedades Starship recebem valores padrão válidos quando uma instância do tipo Starship é criada.
  • Usa o EditContext do formulário para atribuir o modelo quando o componente é inicializado.
  • Valida o formulário no callback OnFieldChanged do contexto para ativar e desativar o botão de envio.
  • Implementa IDisposable e cancela a inscrição do manipulador de eventos no método Dispose. Para obter mais informações, consulte a eliminação de componentes do ASP.NET Core Razor.

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 estiver pré-carregado com valores válidos e você desejar desativar 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 (componenteValidationSummary) é preenchido com campos inválidos depois que o usuário interage com qualquer campo. Solucione esse cenário de uma das seguintes maneiras:

  • Não use um componente ValidationSummary no formulário.
  • Torne o componente ValidationSummary visível quando o botão Enviar for 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";
    }
}

DataAnnotationsValidator comportamento de validação

O DataAnnotationsValidator componente tem a mesma ordem de validação e comportamento de curto-circuito que System.ComponentModel.DataAnnotations.Validatoro . As seguintes regras são aplicadas ao validar uma instância do tipo T:

  1. As propriedades de membro de são validadas, incluindo a validação recursiva de T objetos aninhados.
  2. Os atributos de nível de tipo de T são validados.
  3. O IValidatableObject.Validate método é executado, se T implementá-lo.

Se uma das etapas anteriores produzir um erro de validação, as etapas restantes serão ignoradas.