Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
É ObservableValidator uma classe base que implementa a INotifyDataErrorInfo interface, fornecendo suporte para validar propriedades expostas a outros módulos de aplicação. Também herda de ObservableObject, por isso implementa INotifyPropertyChanged e INotifyPropertyChanging também. Pode ser usado como ponto de partida para todos os tipos de objetos que precisam de suportar tanto notificações de alteração de propriedade como validação de propriedades.
APIs da plataforma:ObservableValidator, ObservableObject
Como funciona
ObservableValidator possui as seguintes características principais:
- Fornece uma implementação base para
INotifyDataErrorInfo, expondo oErrorsChangedevento e as outras APIs necessárias. - Fornece uma série de sobrecargas adicionais
SetProperty(além das proporcionadas pela classe baseObservableObject), que permitem validar automaticamente propriedades e aumentar os eventos necessários antes de atualizarem os seus valores. - Expõe um conjunto de sobrecargas de
TrySetProperty, semelhantes aSetProperty, mas com a capacidade de atualizar a propriedade de destino apenas se a validação for bem-sucedida e de devolver os erros gerados (se existirem) para análise posterior. - Expõe o
ValidatePropertymétodo, o que pode ser útil para ativar manualmente a validação de uma propriedade específica caso o seu valor não tenha sido atualizado, mas a validação dependa do valor de outra propriedade que tenha sido atualizada. - Expõe o
ValidateAllPropertiesmétodo, que executa automaticamente a validação de todas as propriedades públicas da instância atual, desde que tenham pelo menos uma[ValidationAttribute]aplicada. - Revela um
ClearAllErrorsmétodo que pode ser útil ao redefinir um modelo atribuído a algum formulário que o utilizador possa querer preencher novamente. - Oferece vários construtores que permitem passar diferentes parâmetros para inicializar a
ValidationContextinstância que será usada para validar propriedades. Isto pode ser especialmente útil ao utilizar 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 suporta tanto notificações de alterações 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 chamamos o SetProperty<T>(ref T, T, bool, string) método exposto por ObservableValidator, e esse parâmetro adicional bool definido para true indica que também queremos validar a propriedade quando o seu valor é atualizado.
ObservableValidator irá executar automaticamente a validação de cada novo valor usando todas as verificações especificadas com os atributos aplicados à propriedade. Outros componentes (como os controlos da interface) podem então interagir com o modelo de visualização e modificar o seu estado para refletir os erros atualmente presentes no modelo de visualização, registando-se ErrorsChanged e utilizando o GetErrors(string) método para recuperar a lista de erros de cada propriedade que foi modificada.
Métodos personalizados de validação
Por vezes, validar uma propriedade requer que um modelo de visualização tenha acesso a serviços adicionais, dados ou outras APIs. Existem diferentes formas de adicionar 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 [CustomValidationAttribute] tipo pode ser usado para indicar que um método específico precisa de ser invocado para realizar uma 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");
}
}
Neste caso, temos um método estático ValidateName que realiza a validação da Name propriedade através de um serviço que é injetado no nosso viewmodel. Este método recebe o valor da propriedade name e a instância ValidationContext em utilização, que contém elementos como a instância do ViewModel, o nome da propriedade a validar e, opcionalmente, um fornecedor de serviços e alguns sinalizadores personalizados que podem ser utilizados ou definidos. Neste caso, estamos a recuperar a RegistrationForm instância do contexto de validação e, a partir daí, estamos a usar o serviço injetado para validar a propriedade. Note que esta validação será executada ao lado das especificadas nos outros atributos, pelo que somos livres de combinar métodos de validação personalizados e atributos de validação existentes como quisermos.
Atributos de validação personalizados
Outra forma de efetuar a validação personalizada é implementar um [ValidationAttribute] personalizado e, em seguida, inserir a lógica de validação no método substituído IsValid. Isto permite uma flexibilidade extra em comparação com a abordagem descrita acima, pois facilita muito a reutilização do mesmo atributo em vários locais.
Suponha que queremos validar uma propriedade com base no seu valor relativo em relação a outra propriedade no mesmo modelo de visualização. O primeiro passo seria definir um personalizado [GreaterThanAttribute], assim:
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");
}
}
De seguida, podemos adicionar este 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));
}
}
}
Neste caso, temos duas propriedades numéricas que devem estar num intervalo específico e com uma relação específica entre si (A tem de ser maior que B). Adicionámos o novo [GreaterThanAttribute] sobre a primeira propriedade, e também adicionámos uma chamada a ValidateProperty no setter para B, por isso isso A é validado novamente sempre que B muda (uma vez que o seu estado de validação depende disso). Só precisamos destas duas linhas de código no nosso viewmodel para permitir esta validação personalizada, e também temos o benefício de ter um atributo de validação personalizado reutilizável que pode ser útil noutros viewmodels da nossa aplicação. Esta abordagem também ajuda na modularização do código, pois a lógica de validação está agora completamente desacoplada da própria definição do viewmodel.
Exemplos
- Dá uma vista de olhos à aplicação de exemplo (para múltiplos frameworks de interface) para veres o MVVM Toolkit em ação.
- Também podes encontrar mais exemplos nos testes unitários.