Blazor WebAssembly - Validação de Formulários - I


Neste artigo vamos iniciar a criação de uma aplicação Blazor Web Assemply e mostrar como podemos realizar a validação de formulários.

Se você esta chegando agora e não sabe o que é o Blazor leia o artigo ASP .NET Core - Iniciando com o Blazor - Macoratti; se você já conhece e quer saber mais pode fazer o meu curso de Blazor Essencial.  

Validação de formulários

Neste artigo, vamos aprender como criar um formulário em um aplicativo Blazor WebAssembly (WASM). Criaremos um formulário de inscrição do aluno como exemplo. Este formulário oferecerá suporte a validações integradas do lado do cliente com a ajuda de anotações de dados ou Data Annotations.

Também implementaremos um validador personalizado do lado do cliente para o formulário. Junto com o validador do lado do cliente, também adicionaremos um componente validador de formulário customizado para validação de lógica de negócios no aplicativo Blazor WASM.

Vamos usar o VS 2019 Community mas se você estiver no Linux pode usar o VS Code e a NET CLI.

Criando um projeto Blazor Web Assembly

Abra o VS 2019 Community e selecione a opção Create a New Project;

A seguir selecione a opção Blazor WebAssembly App e clique em next;

Informe o nome da solução BlazorValidaForms e clique em Next;

Defina as configurações conforme mostrado na figura abaixo e clique em Create;

Note que marcamos a opção para criar o projeto usando a hospedagem ASP .NET Core.

A final teremos a solução e o projeto criados em uma arquitetura monolítica de projeto único com a seguinte estrutura exibida na janela Solution Explorer:

Criação do modelo de domínio

Vamos agora adicionar uma nova pasta chamada Models no projeto BlazorValidaForms.Shared e a seguir vamos criar o arquivo AlunoRegistro.cs e a classe AlunoRegistro com o seguinte código:

1- AlunoRegistro

using System.ComponentModel.DataAnnotations;

namespace BlazorValidaForms.Shared.Models
{
    public class AlunoRegistro
    {
        [Required]
        [Display(Name = "Nome")]

        public string Nome { get; set; }

        [Required]
        [Display(Name = "Sobrenome")]

        public string Sobrenome { get; set; }

        [Required]
        [EmailAddress]

        public string Email { get; set; }

        [Required]
        public string NomeUsuario { get; set; }

        [Required]
        [RegularExpression(@"^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9]).{8,}$",
            ErrorMessage = "A senha deve ter no mínimo 8 caracteres, no mínimo uma letra
Maiúscula, uma Minúscula e um número.")
]
        public string Password { get; set; }

        [Required]
        [Display(Name = "Confirmar Senha")]
        [Compare("Password", ErrorMessage = "A senha e a confirmação devem conferir.")]

        public string ConfirmaPassword { get; set; }

        [Required]
        public string Genero { get; set; }
    }
}

Na classe AlunoRegistro usamos os atributos Data Annotations  e anotamos todas as propriedades com o atributo [Required]. O atributo [Display] é usado para especificar o nome de exibição das propriedades da classe.

Criar um atributo de validação de formulário personalizado

Agora vamos criar um atributo de validação de formulário personalizado que nos permitirá associar a lógica de validação a uma propriedade do modelo. Vamos anexar uma validação personalizada à propriedade NomeUsuario.  Este validador personalizado restringirá o uso da palavra admin no campo de nome de usuário.

Vamos criar a classe NomeUsuarioValidation dentro da pasta Models no projeto BlazorValidaForms.Shared e a seguir vamos  incluir o seguinte código nesta classe:

using System.ComponentModel.DataAnnotations;

namespace BlazorValidaForms.Shared.Models
{
    public class NomeUsuarioValidation : ValidationAttribute
    {
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            if (!value.ToString().ToLower().Contains("admin"))
            {
                return null;
            }

            return new ValidationResult("O nome do usuário não pode conter a palavra admin",
                new[] { validationContext.MemberName });
        }
    }
}

Neste código a classe NomeUsuarioValidation  herda da classe ValidationAttribute. Além disso, substituímos o método IsValid da desta classe.

Se a validação for bem-sucedida, ou seja, o valor no campo não contém a palavra admin, o método IsValid retornará null.

Se a validação falhar, o método IsValid retornará um objeto do tipo ValidationResult. Este objeto possui dois parâmetros:

  1. A mensagem de erro a ser exibida na IU;
  2. O campo associado a esta mensagem de erro;

A seguir vamos atualizar a classe AlunoRegistro decorando a propriedade NomeUsuario com o atributo NomeUsuarioValidation personalizado.

...
[Required]
[NomeUsuarioValidation]
public string NomeUsuario { get; set; }

...

Criando o controlador : AlunosController

Vamos criar na pasta Controllers do projeto BlazorValidaForms um controlador do tipo - API Controller Empty - incluindo o código abaixo neste controlador:

using BlazorValidaForms.Shared.Models;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;

namespace BlazorValidaForms.Server.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class AlunosController : ControllerBase
    {
        private readonly List<string> nomeUsuarioLista = new();

        public AlunosController()
        {
            nomeUsuarioLista.Add("macoratti");
            nomeUsuarioLista.Add("jessica");
            nomeUsuarioLista.Add("janice");

        }

        [HttpPost]
        public IActionResult Post(AlunoRegistro registro)
        {
            if (nomeUsuarioLista.Contains(registro.NomeUsuario.ToLower()))
            {
                ModelState.AddModelError(nameof(registro.NomeUsuario), "O nome do usuário não esta disponível");
                return BadRequest(ModelState);
            }
            else
            {
                return Ok(ModelState);
            }
        }
    }
}

No código acima, criamos uma lista e a inicializamos no construtor para armazenar três valores de nome de usuário fictícios.

O método Post aceitará um objeto do tipo
AlunoRegistro como parâmetro e a seguir vamos verificar se o nome do usuário fornecido já existe na lista de nomes nomeUsuarioLista.

Se o nome de usuário já existir, vamos incluir a mensagem de erro ao ModelState e retornaremos uma solicitação incorreta do método. Se o nome de usuário estiver disponível, retornaremos a resposta Ok.

Criar um componente validador de formulário personalizado para validação de lógica de negócios

Agora vamos adicionar um componente validador personalizado no projeto do cliente para exibir a mensagem de erro na IU.

Vamos criar a classe CustomFormValidator na pasta BlazorFormsValidation.Client\Shared e incluir o código abaixo nesta classe:

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using System;
using System.Collections.Generic;

namespace BlazorValidaForms.Client.Shared
{
    public class CustomFormValidator : ComponentBase
    {
        private ValidationMessageStore validationMessageStore;

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

        protected override void OnInitialized()
        {
            if (CurrentEditContext == null)
            {
                throw new InvalidOperationException(
                    $"{nameof(CustomFormValidator)} " +
                    $"requer um parametro em cascata do tipo {nameof(EditContext)}.");
            }

            validationMessageStore = new ValidationMessageStore(CurrentEditContext);

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

        public void DisplayFormErrors(Dictionary<string, List<string>> errors)
        {
            foreach (var err in errors)
            {
                validationMessageStore.Add(CurrentEditContext.Field(err.Key), err.Value);
            }

            CurrentEditContext.NotifyValidationStateChanged();
        }

        public void ClearFormErrors()
        {
            validationMessageStore.Clear();
            CurrentEditContext.NotifyValidationStateChanged();
        }
    }
}

O componente CustomFormValidator herda da classe ComponentBase e oferece suporte à validação de formulário em um aplicativo Blazor gerenciando um ValidationMessageStore para o EditContext de um formulário.

O EditContext do formulário é um parâmetro em cascata do componente. A classe EditContext é usada para manter os metadados relacionados a um processo de edição de dados, como sinalizadores para indicar quais campos foram modificados e o conjunto atual de mensagens de validação.

Quando o componente validador for inicializado, um novo ValidationMessageStore será criado para manter a lista atual de erros de formulário.

O armazenamento de mensagens recebe erros quando chamamos o método DisplayFormErrors de nosso componente de registro. Em seguida, os erros serão passados ​​para o método DisplayFormErrors em um dicionário. No dicionário, a Chave é o nome do campo do formulário que contém um ou mais erros. O valor é a lista de erros.

As mensagens de erro serão apagadas se um campo for alterado no formulário quando o evento OnFieldChanged for gerado. Nesse caso, apenas os erros para aquele campo são apagados. Se invocarmos manualmente o método ClearFormErrors, todos os erros serão apagados.

Criando o componente de registro

Vamos adicionar um novo componente Blazor dentro da pasta BlazorValidaForms.Client\Pages. Este componente nos permitirá registrar um novo  aluno.

Nesta pasta vamos criar o arquivo RegistroAluno.razor e a seguir vamos  adicionar um arquivo RegistroAluno.razor.cs na mesma pasta.

Em seguida, inclua o código a seguir dentro do arquivo RegistroAluno.razor.cs:

using BlazorValidaForms.Client.Shared;
using BlazorValidaForms.Shared.Models;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;

namespace BlazorValidaForms.Client.Pages
{
    public class RegistroAlunoBase : ComponentBase
    {
        [Inject]
        private HttpClient Http { get; set; }

        [Inject]
        private ILogger<AlunoRegistro> Logger { get; set; }

        protected AlunoRegistro novoregistro = new();

        protected CustomFormValidator customFormValidator;

        protected bool isRegistroSucesso = false;

        protected async Task RegistraAluno()
        {
            customFormValidator.ClearFormErrors();
            isRegistroSucesso = false;
            try
            {
                var response = await Http.PostAsJsonAsync("api/Alunos", novoregistro);
                var errors = await response.Content.ReadFromJsonAsync<Dictionary<string, List<string>>>();

                if (response.StatusCode == HttpStatusCode.BadRequest && errors.Count > 0)
                {
                    customFormValidator.DisplayFormErrors(errors);
                    throw new HttpRequestException($"Validação falhou. Status Code: {response.StatusCode}");
                }
                else
                {
                    isRegistroSucesso = true;
                    Logger.LogInformation("Registro feito com sucesso");
                }

            }
            catch (Exception ex)
            {
                Logger.LogError(ex.Message);
            }
        }
    }
}

Vamos entender o código criado:

- Declaramos uma classe base para o componente RegistroAlunoBase.
- Injetamos um HttpClient que nos permitirá fazer chamadas HTTP para os endpoints da API.
- Injetamos a interface ILogger que nos permite registrar as mensagens no console do navegador.
- Adicionamos o registro do objeto do tipo AlunoRegistro com o qual vamos armazenar os valores inseridos pelo usuário no formulário.
- Criamos o método
RegistraAluno que invoca o método ClearFormErrors de nossos validadores personalizados para limpar quaisquer erros de formulário existentes.
- Fizemos uma solicitação Post para nosso endpoint de API e passou o objeto de registro para ele.

Se o código de resposta contiver BadRequest ou a contagem de erros for maior que zero, invocamos o método DisplayFormErrors do validador personalizado para exibir o erro na IU. O método lançará uma HttpRequestException. O bloco catch tratará a exceção e o logger registrará o erro no console.

Se não houver uma resposta de erro da API, ela definirá o sinalizador booleano isRegistroSucesso como true e registrará uma mensagem de sucesso no console.

Para concluir vamos incluir o código abaixo no arquivo RegistroAluno.razor :

@page "/registro"
@inherits RegistroAlunoBase

<div class="row justify-content-center">
    <div class="col-md-10">
        <div class="card mt-3 mb-3">
            <div class="card-header">
                <h2>Registro de Alunos</h2>
            </div>
            <div class="card-body">
                @if (isRegistroSucesso)
                {
                    <div class="alert alert-success" role="alert">Registro feito com sucesso</div>
                }

                <EditForm Model="@novoregistro" OnValidSubmit="RegistraAluno">
                    <DataAnnotationsValidator />
                    <CustomFormValidator @ref="customFormValidator" />

                    <div class="form-group row">
                        <label class="control-label col-md-12">Nome</label>
                        <div class="col">
                            <InputText class="form-control" @bind-Value="novoregistro.Nome" />
                            <ValidationMessage For="@(() => novoregistro.Nome)" />
                        </div>
                    </div>

                    <div class="form-group row">
                        <label class="control-label col-md-12">Sobrenome</label>
                        <div class="col">
                            <InputText class="form-control" @bind-Value="novoregistro.Sobrenome" />
                            <ValidationMessage For="@(() => novoregistro.Sobrenome)" />
                        </div>
                    </div>

                    <div class="form-group row">
                        <label class="control-label col-md-12">Email</label>
                        <div class="col">
                            <InputText class="form-control" @bind-Value="novoregistro.Email" />
                            <ValidationMessage For="@(() => novoregistro.Email)" />
                        </div>
                    </div>

                    <div class="form-group row">
                        <label class="control-label col-md-12">Usuário</label>
                        <div class="col">
                            <InputText class="form-control" @bind-Value="novoregistro.NomeUsuario" />
                            <ValidationMessage For="@(() => novoregistro.NomeUsuario)" />
                        </div>
                    </div>

                    <div class="form-group row">
                        <label class="control-label col-md-12">Password</label>
                        <div class="col">
                            <InputText type="password" class="form-control" @bind-Value="novoregistro.Password"></InputText>
                            <ValidationMessage For="@(() => novoregistro.Password)" />
                        </div>
                    </div>

                    <div class="form-group row">
                        <label class="control-label col-md-12">Confirar Password</label>
                        <div class="col">
                            <InputText type="password" class="form-control" @bind-Value="novoregistro.ConfirmaPassword"></InputText>
                            <ValidationMessage For="@(() => novoregistro.ConfirmaPassword)" />
                        </div>
                    </div>

                    <div class="form-group row">
                        <label class="control-label col-md-12">Gênero</label>
                        <div class="col">
                            <InputSelect class="form-control" @bind-Value="novoregistro.Genero">
                                <option value="-- Select City --">-- Selecionar --</option>
                                <option value="Male">Masculino</option>
                                <option value="Female">Feminino</option>
                            </InputSelect>
                            <ValidationMessage For="@(() => novoregistro.Genero)" />
                        </div>
                    </div>

                    <div class="form-group" align="right">
                        <button type="submit" class="btn btn-success">Registrar</button>
                    </div>
                </EditForm>
            </div>
        </div>
    </div>
</div>

Na página de template do componente, definimos a rota do componente como /registro. Aqui, estamos usando um Card do Bootstrap para exibir o componente de registro.

O componente EditForm é usado para construir um formulário. O Blazor também fornece componentes de entrada de formulário integrados, como InputText, InputSelect, InputDate, InputTextArea, InputCheckbox e assim por diante. Usamos o atributo Model para definir um objeto do modelo  no formulário. O método RegistraAluno será invocado no envio válido do formulário.

O componente DataAnnotationsValidator é usado para validar o formulário usando os atributos de anotações de dados na classe Model que está associada ao formulário. Para usar o componente validador personalizado no formulário, forneça o nome do validador como o nome da tag e forneça a referência da variável local para o atributo @ref.

Estamos usando o componente InputText para exibir uma caixa de texto no formulário. O componente InputSelect é usado para exibir a lista suspensa para selecionar o valor do gênero. Para vincular a propriedade modal aos campos do formulário, usamos o atributo bind-Value. O componente ValidationMessage é usado para exibir a mensagem de validação abaixo de cada campo no formulário Blazor.

Para poder testar vamos alterar o código do componente NavMenu.razor e definir um link para acessar o componente de registro de aluno:

...

<li class="nav-item px-3">
    <NavLink class="nav-link" href="registra">
         <span class="oi oi-plus" aria-hidden="true"></span> Registra Aluno
     </NavLink>
</li>
...

Executando o projeto iremos obter o seguinte resultado:

Temos assim o nosso formulário de registro de alunos criado onde aplicamos alguns recursos de validação

Pegue o projeto aqui :  BlazorValidaForms.zip   (sem as referências)

"Guia-me na tua verdade, e ensina-me, pois tu és o Deus da minha salvação; por ti estou esperando todo o dia."
Salmos 25:5

Referências:


José Carlos Macoratti