Compartir a través de


ObservableValidator

El ObservableValidator es una clase base que implementa la interfaz INotifyDataErrorInfo, lo que proporciona compatibilidad para validar las propiedades expuestas a otros módulos de aplicación. También hereda de ObservableObject, por lo que también implementa INotifyPropertyChanged y INotifyPropertyChanging. Se puede usar como punto de partida para todos los tipos de objetos que necesitan admitir las notificaciones de cambio de propiedad y la validación de propiedades.

API de plataforma: ObservableValidator, ObservableObject

Funcionamiento

ObservableValidator tiene las siguientes características principales:

  • Proporciona una implementación base para INotifyDataErrorInfo, que expone el evento ErrorsChanged y las otras API necesarias.
  • Proporciona una serie de sobrecargas SetProperty adicionales (sobre las proporcionadas por la clase base ObservableObject ), que ofrecen la capacidad de validar automáticamente las propiedades y generar los eventos necesarios antes de actualizar sus valores.
  • Expone una serie de sobrecargas TrySetProperty, que son similares a SetProperty pero con la capacidad de actualizar solo la propiedad de destino si la validación se realiza correctamente y devolver los errores generados (si los hay) para una inspección adicional.
  • Expone el método ValidateProperty, que puede ser útil para desencadenar manualmente la validación de una propiedad específica en caso de que su valor no se haya actualizado, pero su validación depende del valor de otra propiedad que se haya actualizado en su lugar.
  • Expone el método ValidateAllProperties, que ejecuta automáticamente la validación de todas las propiedades de instancia pública de la instancia actual, siempre que tengan al menos un [ValidationAttribute] aplicado a ellas.
  • Expone un método ClearAllErrors que puede ser útil al restablecer un modelo enlazado a algún formulario que el usuario podría querer rellenar de nuevo.
  • Ofrece una serie de constructores que permiten pasar parámetros diferentes para inicializar la instancia ValidationContext que se usará para validar las propiedades. Esto puede ser especialmente útil cuando se usan atributos de validación personalizados que podrían requerir servicios o opciones adicionales para funcionar correctamente.

Propiedad simple

Este es un ejemplo de cómo implementar una propiedad que admita las notificaciones de cambio, así como la validación:

public class RegistrationForm : ObservableValidator
{
    private string name;

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

Aquí llamamos al método SetProperty<T>(ref T, T, bool, string) expuesto por ObservableValidator, y ese parámetro bool adicional establecido en true indica que también queremos validar la propiedad cuando se actualiza su valor. ObservableValidator ejecutará automáticamente la validación en cada nuevo valor mediante todas las comprobaciones especificadas con los atributos aplicados a la propiedad. Otros componentes (como los controles de interfaz de usuario) pueden interactuar con el modelo de vista y modificar su estado para reflejar los errores presentes actualmente en el modelo de vista, registrando en ErrorsChanged y usando el método GetErrors(string) para recuperar la lista de errores de cada propiedad que se ha modificado.

Métodos de validación personalizados

A veces, la validación de una propiedad requiere que un modelo de vista tenga acceso a servicios, datos u otras API adicionales. Hay diferentes maneras de agregar validación personalizada a una propiedad, según el escenario y el nivel de flexibilidad que se requiere. Este es un ejemplo de cómo se puede usar el tipo [CustomValidationAttribute] para indicar que es necesario invocar un método específico para realizar una validación adicional de una propiedad:

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");
    }
}

En este caso, tenemos un método estáticoValidateName que realizará la validación en la propiedad Name a través de un servicio que se inserta en nuestro modelo de vista. Este método recibe el namevalor de la propiedad y la instancia ValidationContext en uso, que contiene elementos como la instancia de modelo de vista, el nombre de la propiedad que se va a validar y, opcionalmente, un proveedor de servicios y algunas marcas personalizadas que podemos usar o establecer. En este caso, estamos recuperando la instancia RegistrationForm del contexto de validación y, desde allí, usamos el servicio insertado para validar la propiedad. Tenga en cuenta que esta validación se ejecutará junto a las especificadas en los demás atributos, por lo que podemos combinar métodos de validación personalizados y atributos de validación existentes, pero nos gusta.

Atributos de validación personalizados

Otra manera de realizar la validación personalizada es implementar un [ValidationAttribute] personalizado e insertar la lógica de validación en el método IsValid invalidado. Esto permite una flexibilidad adicional en comparación con el enfoque descrito anteriormente, ya que facilita la reutilización del mismo atributo en varios lugares.

Supongamos que queríamos validar una propiedad en función de su valor relativo con respecto a otra propiedad en el mismo modelo de vista. El primer paso sería definir un [GreaterThanAttribute]personalizado, de la siguiente manera:

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");
    }
}

A continuación, podemos agregar este atributo a nuestro modelo de vista:

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));
        }
    }
}

En este caso, tenemos dos propiedades numéricas que deben estar en un intervalo específico y con una relación específica entre sí (A debe ser mayor que B). Hemos agregado el nuevo [GreaterThanAttribute] sobre la primera propiedad y también hemos agregado una llamada a ValidateProperty en el establecedor para B, de modo que A se valide de nuevo cada vez que B cambie (ya que su estado de validación depende de él). Solo necesitamos estas dos líneas de código en nuestro modelo de vista para habilitar esta validación personalizada y también obtenemos la ventaja de tener un atributo de validación personalizado reutilizable que podría ser útil en otros modelos de vista de nuestra aplicación también. Este enfoque también ayuda con la modularización de código, ya que la lógica de validación ahora está completamente desacoplada de la propia definición del modelo de vista.

Ejemplos

  • Consulte la aplicación de ejemplo (para varios marcos de interfaz de usuario) para ver el kit de herramientas de MVVM en acción.
  • También puede encontrar más ejemplos en las pruebas unitarias.