Compartilhar via


ObservableValidator

O ObservableValidator é uma classe base que implementa a interface INotifyDataErrorInfo, fornecendo suporte para validar propriedades expostas a outros módulos de aplicativo. Ele também herda de ObservableObject, portanto, implementa INotifyPropertyChanged e INotifyPropertyChanging também. Ele pode ser usado como ponto de partida para todos os tipos de objetos que precisam dar suporte a notificações de alteração de propriedade e à validação de propriedade.

APIs da plataforma: ObservableValidator, ObservableObject

Como ele funciona

O ObservableValidator tem os seguintes recursos principais:

  • Ele fornece uma implementação base para INotifyDataErrorInfo, expondo o evento ErrorsChanged e as outras APIs necessárias.
  • Ele fornece uma série de sobrecargas SetProperty adicionais (sobre as fornecidas pela classe base ObservableObject), que oferecem a capacidade de validar automaticamente as propriedades e gerar os eventos necessários antes de atualizar seus valores.
  • Ele expõe uma série de sobrecargas TrySetProperty, que são semelhantes a SetProperty, mas com a capacidade de atualizar apenas a propriedade de destino se a validação for bem-sucedida e retornar os erros gerados (se houver) para inspeção adicional.
  • Ele expõe o método ValidateProperty, que pode ser útil para disparar manualmente a validação de uma propriedade específica caso seu valor não tenha sido atualizado, mas sua validação depende do valor de outra propriedade que, em vez disso, foi atualizada.
  • Ele expõe o método ValidateAllProperties, que executa automaticamente a validação de todas as propriedades de instância pública na instância atual, desde que tenham pelo menos um [ValidationAttribute] aplicado a elas.
  • Ele expõe um método ClearAllErrors que pode ser útil ao redefinir um modelo associado a algum formulário que o usuário possa querer preencher novamente.
  • Ele oferece vários construtores que permitem a passagem de parâmetros diferentes para inicializar a instância ValidationContext que será usada para validar as propriedades. Isso pode ser especialmente útil ao usar atributos de validação personalizados que podem exigir serviços ou opções adicionais para funcionar corretamente.

Propriedade simples

Aqui está um exemplo de como implementar uma propriedade que dá suporte a notificações de alteração, bem como à validação:

public class RegistrationForm : ObservableValidator
{
    private string name;

    [Required]
    [MinLength(2)]
    [MaxLength(100)]
    public string Name
    {
        get => name;
        set => SetProperty(ref name, value, true);
    }
}

Aqui, estamos chamando o método SetProperty<T>(ref T, T, bool, string) exposto por ObservableValidator, e esse parâmetro bool adicional definido como true indica que também queremos validar a propriedade quando seu valor for atualizado. O ObservableValidator executará automaticamente a validação em cada novo valor usando todas as verificações especificadas com os atributos aplicados à propriedade. Outros componentes (como controles de interface do usuário) podem interagir com o viewmodel e modificar seu estado para refletir os erros atualmente presentes no viewmodel, registrando ErrorsChanged e usando o método GetErrors(string) para recuperar a lista de erros de cada propriedade que foi modificada.

Métodos de validação personalizadas

Às vezes, a validação de uma propriedade requer um viewmodel para ter acesso a serviços, dados ou outras APIs adicionais. Há diferentes maneiras de adicionar a validação personalizada a uma propriedade, dependendo do cenário e do nível de flexibilidade necessário. Aqui está um exemplo de como o tipo [CustomValidationAttribute] pode ser usado para indicar que um método específico precisa ser invocado para executar a validação adicional de uma propriedade:

public class RegistrationForm : ObservableValidator
{
    private readonly IFancyService service;

    public RegistrationForm(IFancyService service)
    {
        this.service = service;
    }

    private string name;

    [Required]
    [MinLength(2)]
    [MaxLength(100)]
    [CustomValidation(typeof(RegistrationForm), nameof(ValidateName))]
    public string Name
    {
        get => this.name;
        set => SetProperty(ref this.name, value, true);
    }

    public static ValidationResult ValidateName(string name, ValidationContext context)
    {
        RegistrationForm instance = (RegistrationForm)context.ObjectInstance;
        bool isValid = instance.service.Validate(name);

        if (isValid)
        {
            return ValidationResult.Success;
        }

        return new("The name was not validated by the fancy service");
    }
}

Nesse caso, temos um método estático ValidateName que executará a validação na propriedade Name por meio de um serviço que é injetado em nosso viewmodel. Esse método recebe o valor da propriedade name e a instância ValidationContext em uso, que contém itens como a instância do viewmodel, o nome da propriedade que está sendo validada e, opcionalmente, um provedor de serviços e alguns sinalizadores personalizados que podemos usar ou definir. Nesse caso, estamos recuperando a instância RegistrationForm do contexto de validação e, a partir daí, estamos usando o serviço injetado para validar a propriedade. Observe que essa validação será executada ao lado das especificadas nos outros atributos, portanto, somos livres para combinar métodos de validação personalizados e atributos de validação existentes da maneira que quisermos.

Atributos de validação personalizados

Outra maneira de fazer a validação personalizada é implementando um [ValidationAttribute] personalizado e inserindo a lógica de validação no método IsValid substituído. Isso permite uma flexibilidade extra em comparação com a abordagem descrita acima, pois torna muito fácil apenas reutilizar o mesmo atributo em vários lugares.

Suponha que queríamos validar uma propriedade com base em seu valor relativo em relação a outra propriedade no mesmo viewmodel. A primeira etapa seria definir um [GreaterThanAttribute] personalizado, assim como:

public sealed class GreaterThanAttribute : ValidationAttribute
{
    public GreaterThanAttribute(string propertyName)
    {
        PropertyName = propertyName;
    }

    public string PropertyName { get; }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        object
            instance = validationContext.ObjectInstance,
            otherValue = instance.GetType().GetProperty(PropertyName).GetValue(instance);

        if (((IComparable)value).CompareTo(otherValue) > 0)
        {
            return ValidationResult.Success;
        }

        return new("The current value is smaller than the other one");
    }
}

Em seguida, podemos adicionar esse atributo ao nosso viewmodel:

public class ComparableModel : ObservableValidator
{
    private int a;

    [Range(10, 100)]
    [GreaterThan(nameof(B))]
    public int A
    {
        get => this.a;
        set => SetProperty(ref this.a, value, true);
    }

    private int b;

    [Range(20, 80)]
    public int B
    {
        get => this.b;
        set
        {
            SetProperty(ref this.b, value, true);
            ValidateProperty(A, nameof(A));
        }
    }
}

Nesse caso, temos duas propriedades numéricas que devem estar em um intervalo específico e com uma relação específica entre si (A precisa ser maior que B). Adicionamos o novo [GreaterThanAttribute] sobre a primeira propriedade e também adicionamos uma chamada a ValidateProperty no setter para B, de modo que A seja validado novamente sempre que B for alterado (já que seu status de validação depende dele). Só precisamos dessas duas linhas de código em nosso viewmodel para habilitar essa validação personalizada e também temos o benefício de ter um atributo de validação personalizado reutilizável que pode ser útil em outros viewmodels no nosso aplicativo também. Essa abordagem também ajuda na modularização de código, pois a lógica de validação agora é completamente dissociada da própria definição do viewmodel.

Exemplos

  • Confira o aplicativo de exemplo (para várias estruturas de interface do usuário) para ver o Kit de Ferramentas MVVM em ação.
  • Você também pode encontrar mais exemplos nos testes de unidade.