2010-01-21 14 views
10

Estoy empezando a implementar la validación en mi proyecto WPF a través de la interfaz IDataErrorInfo. Mi objeto comercial contiene múltiples propiedades con información de validación. ¿Cómo obtengo una lista de TODOS los mensajes de error asociados con el objeto? Mi idea es que esa es la propiedad Error, pero no puedo rastrear a nadie que use esto para informar sobre propiedades múltiples.¿Cómo puedo definir una propiedad de error IDataErrorInfo para múltiples propiedades BO?

Gracias!

public string this[string property] 
    { 
     get { 

      string msg = null; 
      switch (property) 
      { 
       case "LastName": 
        if (string.IsNullOrEmpty(LastName)) 
         msg = "Need a last name"; 
        break; 
       case "FirstName": 
        if (string.IsNullOrEmpty(LastName)) 
         msg = "Need a first name"; 
        break; 

       default: 
        throw new ArgumentException(
         "Unrecognized property: " + property); 
      } 
      return msg; 

     } 
    } 

    public string Error 
    { 
     get 
     { 
      return null ; 
     } 
    } 

Respuesta

11

Sí, veo dónde podría usar el indexador. No es una mala manera de ir, supongo. Sin embargo, estaba realmente centrado en la propiedad 'Error'. Me gusta la noción de tener los errores contenidos dentro del objeto comercial. Creo que lo que quiero hacer no lo existe de forma nativa, por lo que acabo de crear un diccionario de errores (se actualiza cada vez que una propiedad cambia) en el objeto y dejar que devolver el error de un CarriageReturn lista de errores delimitada, así:

public string this[string property] 
    { 
     get { 

      string msg = null; 
      switch (property) 
      { 
       case "LastName": 
        if (string.IsNullOrEmpty(LastName)) 
         msg = "Need a last name"; 
        break; 
       case "FirstName": 
        if (string.IsNullOrEmpty(FirstName)) 
         msg = "Need a first name"; 
        break; 
       default: 
        throw new ArgumentException(
         "Unrecognized property: " + property); 
      } 

      if (msg != null && !errorCollection.ContainsKey(property)) 
       errorCollection.Add(property, msg); 
      if (msg == null && errorCollection.ContainsKey(property)) 
       errorCollection.Remove(property); 

      return msg; 
     } 
    } 

    public string Error 
    { 
     get 
     { 
      if(errorCollection.Count == 0) 
       return null; 

      StringBuilder errorList = new StringBuilder(); 
      var errorMessages = errorCollection.Values.GetEnumerator(); 
      while (errorMessages.MoveNext()) 
       errorList.AppendLine(errorMessages.Current); 

      return errorList.ToString(); 
     } 
    } 
+0

Esto es bueno. Sería mejor mantener la función ErrorCollection actualizada con el último mensaje de error (si la clave ya existe y el mensaje no es nulo). – danjarvis

+0

La propiedad 'Error' se puede optimizar para:' get {return string.Join (Environment.NewLine, errorCollection.Values); } ', si está vacío, solo devuelve" ", que (como está escrito en la documentación de la interfaz) no se considera un error. – Tatranskymedved

1

Mi entendimiento es que para utilizar esta interfaz, enumerar las propiedades del objeto, y llame a la indexador una vez para cada propiedad. Es responsabilidad del que llama agregar todos los mensajes de error.

+0

Gracias. Estás en lo correcto. Estaba buscando una solución que esté más encapsulada dentro del objeto comercial, ver arriba. Tal vez no sea la solución más perfecta para la separación de preocupaciones. – Bob

11

Creo que es mucho más fácil usar los atributos de Validación.

class MyBusinessObject { 
    [Required(ErrorMessage="Must enter customer")] 
    public string Customer { get; set; } 

    [Range(10,99, ErrorMessage="Price must be between 10 and 99")] 
    public decimal Price { get; set; } 

    // I have also created some custom attributes, e.g. validate paths 
    [File(FileValidation.IsDirectory, ErrorMessage = "Must enter an importfolder")] 
    public string ImportFolder { get; set; } 

    public string this[string columnName] { 
     return InputValidation<MyBusinessObject>.Validate(this, columnName); 
    } 

    public ICollection<string> AllErrors() { 
     return InputValidation<MyBusinessObject>.Validate(this); 
    } 
} 

El ayudante InputValidation clase tiene este aspecto

internal static class InputValidation<T> 
    where T : IDataErrorInfo 
{ 
    /// <summary> 
    /// Validate a single column in the source 
    /// </summary> 
    /// <remarks> 
    /// Usually called from IErrorDataInfo.this[]</remarks> 
    /// <param name="source">Instance to validate</param> 
    /// <param name="columnName">Name of column to validate</param> 
    /// <returns>Error messages separated by newline or string.Empty if no errors</returns> 
    public static string Validate(T source, string columnName) { 
     KeyValuePair<Func<T, object>, ValidationAttribute[]> validators; 
     if (mAllValidators.TryGetValue(columnName, out validators)) { 
      var value = validators.Key(source); 
      var errors = validators.Value.Where(v => !v.IsValid(value)).Select(v => v.ErrorMessage ?? "").ToArray(); 
      return string.Join(Environment.NewLine, errors); 
     } 
     return string.Empty; 
    } 

    /// <summary> 
    /// Validate all columns in the source 
    /// </summary> 
    /// <param name="source">Instance to validate</param> 
    /// <returns>List of all error messages. Empty list if no errors</returns> 
    public static ICollection<string> Validate(T source) { 
     List<string> messages = new List<string>(); 
     foreach (var validators in mAllValidators.Values) { 
      var value = validators.Key(source); 
      messages.AddRange(validators.Value.Where(v => !v.IsValid(value)).Select(v => v.ErrorMessage ?? "")); 
     } 
     return messages; 
    } 

    /// <summary> 
    /// Get all validation attributes on a property 
    /// </summary> 
    /// <param name="property"></param> 
    /// <returns></returns> 
    private static ValidationAttribute[] GetValidations(PropertyInfo property) { 
     return (ValidationAttribute[])property.GetCustomAttributes(typeof(ValidationAttribute), true); 
    } 

    /// <summary> 
    /// Create a lambda to receive a property value 
    /// </summary> 
    /// <param name="property"></param> 
    /// <returns></returns> 
    private static Func<T, object> CreateValueGetter(PropertyInfo property) { 
     var instance = Expression.Parameter(typeof(T), "i"); 
     var cast = Expression.TypeAs(Expression.Property(instance, property), typeof(object)); 
     return (Func<T, object>)Expression.Lambda(cast, instance).Compile(); 
    } 

    private static readonly Dictionary<string, KeyValuePair<Func<T, object>, ValidationAttribute[]>> mAllValidators; 

    static InputValidation() { 
     mAllValidators = new Dictionary<string, KeyValuePair<Func<T, object>, ValidationAttribute[]>>(); 
     foreach (var property in typeof(T).GetProperties()) { 
      var validations = GetValidations(property); 
      if (validations.Length > 0) 
       mAllValidators.Add(property.Name, 
         new KeyValuePair<Func<T, object>, ValidationAttribute[]>(
         CreateValueGetter(property), validations)); 
     }  
    } 
} 
+1

Esta es otra buena solución, gracias. Me funcionó en una propiedad de máquina virtual que el estándar IDataErrorInfo no. Si alguien usa esto, necesita agregar una referencia a System.ComponentModel.DataAnnotations. – Bob

+0

Esto es excelente: viniendo del mundo de MVC, es genial poder usar la misma convención. – Cody

Cuestiones relacionadas