Compare validation com navigation

Qualquer aplicativo que aceite entrada de usuários deve garantir que a entrada seja válida. Um aplicativo pode, por exemplo, verificar se a entrada que contém apenas caracteres em um intervalo específico, tem um determinado comprimento ou corresponde a um formato específico. Sem validação, um usuário pode fornecer dados que causam falha no aplicativo. A validação adequada impõe regras de negócio e pode ajudar a impedir que um invasor injete dados maliciosos.

No contexto do padrão Model-View-ViewModel (MVVM), um Model ou ViewModel geralmente será necessário para executar a validação de dados e sinalizar quaisquer erros de validação para a View para que o usuário possa corrigi-los. A imagem abaixo mostra as classes envolvidas no processo de validação.

As propriedades do ViewModel que requerem validação são do tipo ValidatableObject<T>, e cada instância ValidatableObject<T> tem regras de validação adicionadas à sua propriedade Validations. A validação é chamada do ViewModel chamando o método Validate da instância ValidatableObject<T>, que recupera as regras de validação e as executa na propriedade ValidatableObject<T>.Value. Quaisquer erros de validação são colocados na propriedade Errors da instância ValidatableObject<T> e a propriedade IsValid da instância ValidatableObject<T> é atualizada para indicar se a validação foi bem-sucedida ou falhou. O código a seguir mostra a implementação do ValidatableObject<T>:

using CommunityToolkit.Mvvm.ComponentModel;

namespace RMLib.Validations;

public class ValidatableObject<T> : ObservableObject, IValidity
{
  private IEnumerable<string> _errors;
  private bool _isValid;
  private T _value;

  public List<IValidationRule<T>> Validations { get; } = new();

  public IEnumerable<string> Errors
  {
    get => _errors;
    private set => SetProperty(ref _errors, value);
  }

  public bool IsValid
  {
    get => _isValid;
    private set => SetProperty(ref _isValid, value);
  }

  public T Value
  {
    get => _value;
    set => SetProperty(ref _value, value);
  }

  public ValidatableObject()
  {
    _isValid = true;
    _errors = Enumerable.Empty<string>();
  }

  public bool Validate()
  {
    Errors = Validations
        ?.Where(v => !v.Check(Value))
        ?.Select(v => v.ValidationMessage)
        ?.ToArray()
        ?? Enumerable.Empty<string>();

    IsValid = !Errors.Any();

    return IsValid;
  }
}

Clique para acessar o arquivo

A notificação de alteração de propriedade é fornecida pela classe ObservableObject e, portanto, um controle Entry pode se vincular à propriedade IsValid da instância ValidatableObject<T> na classe do ViewModel para ser notificado se os dados inseridos são válidos ou não.

As regras de validação são especificadas criando uma classe que deriva da interface IValidationRule<T>, que é mostrada no exemplo de código a seguir:

public interface IValidationRule<T>
{
    string ValidationMessage { get; set; }
    bool Check(T value);
}

Clique para acessar o arquivo

Essa interface especifica que uma classe de regra de validação deve fornecer um método Check booleano que é usado para executar a validação necessária e uma propriedade ValidationMessage cujo valor é a mensagem de erro de validação que será exibida se a validação falhar.

O exemplo de código a seguir mostra a regra IsNotNullOrEmptyRule<T> de validação, que é usada para realizar a validação do nome de usuário e senha inseridos pelo usuário na RMSLoginPage:

public class IsNotNullOrEmptyRule<T> : IValidationRule<T>
{
    public string ValidationMessage { get; set; }

    public bool Check(T value) =>
        value is string str && !string.IsNullOrWhiteSpace(str);
}

Clique para acessar o arquivo

O método Check retorna um booleano indicando se o argumento de valor é nulo, vazio ou consiste apenas em caracteres de espaço em branco.

Às vezes, a validação pode envolver propriedades dependentes. Um exemplo de propriedades dependentes é quando o conjunto de valores válidos para a propriedade A depende do valor específico que foi definido na propriedade B. Verificar se o valor da propriedade A é um dos valores permitidos envolveria a recuperação do valor da propriedade B. Além disso, quando o valor da propriedade B fosse alterado, a propriedade A precisaria ser revalidada.

No aplicativo multiplataforma MinhaQualidadeMaui, as propriedades do ViewModel que requerem validação são declaradas como sendo do tipo ValidatableObject<T>, onde T é o tipo do dado a ser validado. O exemplo de código a seguir mostra um exemplo de duas dessas propriedades:

public ValidatableObject<string> Login { get; private set; }
public ValidatableObject<string> Senha { get; private set; }

Clique para acessar o arquivo

Para que a validação ocorra, as regras de validação devem ser adicionadas à coleção Validations de cada instância ValidatableObject<T>, conforme demonstrado no exemplo de código a seguir:

private void AddValidations()
{
  Login.Validations.Add(new IsNotNullOrEmptyRule<string> { ValidationMessage = "O campo Usuário é obrigatório." });
  Senha.Validations.Add(new IsNotNullOrEmptyRule<string> { ValidationMessage = "O campo Senha é obrigatório." });
}

Clique para acessar o arquivo

Este método adiciona a regra de validação IsNotNullOrEmptyRule<T> à coleção Validations de cada instância ValidatableObject<T>, especificando valores para a propriedade ValidationMessage da regra de validação, que especifica a mensagem de erro de validação que será exibida se a validação falhar.

A validação também é acionada automaticamente sempre que uma propriedade vinculada é alterada. Por exemplo, quando uma ligação bidirecional na RMSLoginPage define a propriedade Login ou Senha, a validação é acionada. O exemplo de código a seguir demonstra como isso ocorre:

<framecustom:RMSCustomEntry 
    x:Name="entryLogin"
    ...       
    FontFamily="NunitoSans-Regular.ttf#NunitoSans-Regular" >
    <framecustom:RMSCustomEntry.Behaviors>
        <mct:EventToCommandBehavior
                EventName="TextChanged"
                Command="{Binding ValidateCommand}" />
    </framecustom:RMSCustomEntry.Behaviors>
</framecustom:RMSCustomEntry>

Clique para acessar o arquivo

O controle CustomEntry é vinculado à propriedade Login.Value da instância ValidatableObject<T> e a coleção Behaviors do controle tem uma instância EventToCommandBehavior adicionada a ela. Esse comportamento executa o ValidateCommand em resposta ao disparo do evento TextChanged no CustomEntry, que é gerado quando o texto no CustomEntry é alterado. Por sua vez, o ValidateCommand executa o método OnValidate, que executa o método Validate na instância ValidatableObject<T>. Portanto, toda vez que o usuário insere um caractere no controle CustomEntry para o nome de usuário, é realizada a validação dos dados inseridos.

O aplicativo multiplataforma MinhaQualidadeMaui notifica o usuário sobre quaisquer erros de validação, destacando o controle que contém os dados inválidos com uma borda vermelha e exibindo uma mensagem de erro que informa ao usuário por que os dados são inválidos abaixo do controle que contém os dados inválidos. Quando os dados inválidos são corrigidos, a borda volta ao estado padrão e a mensagem de erro é removida. A imagem abaixo mostra a RMSLoginPage quando há erros de validação.