2011-08-15 18 views
5

Estoy escribiendo una aplicación de WPF y quiero utilizar las anotaciones de datos para especificar cosas como Required campos, Range, etc.DataAnnotations combinando y IDataErrorInfo para WPF

Mis clases ViewModel utilizar la interfaz normal INotifyPropertyChanged y puedo validar la todo el objeto con suficiente facilidad usando C# 4 Validator, pero también me gustaría que los campos resalten en rojo si no se validan correctamente. Encontré esta publicación en el blog aquí (http://blogs.microsoft.co.il/blogs/tomershamam/archive/2010/10/28/wpf-data-validation-using-net-data-annotations-part-ii.aspx) que habla sobre cómo escribir su modelo de vista base para implementar IDataErrorInfo y simplemente usar el Validator, pero la implementación no se compila realmente ni puedo ver cómo funcionaría. El método en cuestión es la siguiente:

/// <summary> 
    /// Validates current instance properties using Data Annotations. 
    /// </summary> 
    /// <param name="propertyName">This instance property to validate.</param> 
    /// <returns>Relevant error string on validation failure or <see cref="System.String.Empty"/> on validation success.</returns> 
    protected virtual string OnValidate(string propertyName) 
    { 
     if (string.IsNullOrEmpty(propertyName)) 
     { 
      throw new ArgumentException("Invalid property name", propertyName); 
     } 

     string error = string.Empty; 
     var value = GetValue(propertyName); 
     var results = new List<ValidationResult>(1); 
     var result = Validator.TryValidateProperty(
      value, 
      new ValidationContext(this, null, null) 
      { 
       MemberName = propertyName 
      }, 
      results); 

     if (!result) 
     { 
      var validationResult = results.First(); 
      error = validationResult.ErrorMessage; 
     } 

     return error; 
    } 

El problema es GetValue no se proporciona. Podría estar hablando del GetValue que aparece cuando hereda DependencyObject, pero la sintaxis aún no funciona (espera que pase DependencyProperty como parámetro) pero estoy usando propiedades regulares de CLR con OnPropertyChanged("MyProperty") que se invoca en el setter.

¿Hay una buena manera de conectar la validación a la interfaz IDataErrorInfo?

Respuesta

5

Utilizando su código anterior como punto de partida lo hice funcionar a través de IDataErrorInfo.

Su problema se centró en obtener el valor de la propiedad cuando solo tiene el nombre de la propiedad, la reflexión puede ayudar aquí.

public string this[string property] 
{ 
    get 
    { 
     PropertyInfo propertyInfo = this.GetType().GetProperty(property); 
     var results = new List<ValidationResult>(); 

     var result = Validator.TryValidateProperty(
           propertyInfo.GetValue(this, null), 
           new ValidationContext(this, null, null) 
           { 
            MemberName = property 
           }, 
           results); 

     if (!result) 
     { 
     var validationResult = results.First(); 
     return validationResult.ErrorMessage; 
     } 

     return string.Empty; 
    } 
} 
+0

Esto es más o menos lo que terminé haciendo, pero no funcionará si tiene propiedades de dependencia en el Modelo de Vista, así que tiene que probar en ambos sentidos. No es tan elegante como esperaba, pero a veces es lo que tienes que hacer. –

1

Sé que este post es viejo, pero recientemente he resuelto este problema con la ayuda de este post, al tiempo que algunas optimizaciones en el camino. Me gustaría compartir la implementación de ViewModelBase de IDataErrorInfo. Utiliza expresiones compiladas para los captadores de propiedades que acelera el acceso al valor de la propiedad. También disparo las compilaciones de expresiones en el hilo de fondo cuando el tipo se carga en la memoria. Afortunadamente, termina la compilación antes de la primera llamada a OnValidate, ya que la compilación de expresiones puede ser un poco lenta. Gracias y vítores.

public abstract class ViewModelBase<TViewModel> : IDataErrorInfo 
    where TViewModel : ViewModelBase<TViewModel> 
{ 
    string IDataErrorInfo.Error 
    { 
     get { throw new NotSupportedException("IDataErrorInfo.Error is not supported, use IDataErrorInfo.this[propertyName] instead."); } 
    } 

    string IDataErrorInfo.this[string propertyName] 
    { 
     get { return OnValidate(propertyName, propertyGetters.Result[propertyName]((TViewModel)this)); } 
    } 

    private static Task<Dictionary<string, Func<TViewModel, object>>> propertyGetters = Task.Run(() => 
    { 
     return typeof(TViewModel).GetProperties() 
      .Select(propertyInfo => 
      { 
       var viewModel = Expression.Parameter(typeof(TViewModel)); 
       var property = Expression.Property(viewModel, propertyInfo); 
       var castToObject = Expression.Convert(property, typeof(object)); 
       var lambda = Expression.Lambda(castToObject, viewModel); 

       return new 
       { 
        Key = propertyInfo.Name, 
        Value = (Func<TViewModel, object>)lambda.Compile() 
       }; 
      }) 
      .ToDictionary(pair => pair.Key, pair => pair.Value); 
    }); 

    protected virtual string OnValidate(string propertyName, object propertyValue) 
    { 
     var validationResults = new List<ValidationResult>(); 

     var validationContext = new ValidationContext(this, null, null) { MemberName = propertyName }; 

     if (!Validator.TryValidateProperty(propertyValue, validationContext, validationResults)) 
     { 
      return validationResults.First().ErrorMessage; 
     } 

     return string.Empty; 
    } 
}