2010-01-21 8 views
10

Я начинаю выполнять проверку в моем проекте WPF через интерфейс IDataErrorInfo. Мой бизнес-объект содержит несколько свойств с информацией о проверке. Как получить список ВСЕХ сообщений об ошибках, связанных с объектом. Моя мысль заключается в том, что это свойство Error, но я не могу отследить кого-либо, использующего это для сообщения о нескольких свойствах.Как определить свойство ошибки IDataErrorInfo для нескольких свойств BO

Спасибо!

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

ответ

11

Да, я вижу, где вы можете использовать индексатор. Неплохой путь, я думаю. Тем не менее, я действительно сосредоточился на свойстве «Ошибка». Мне нравится представление об ошибках, содержащихся в бизнес-объекте. Я думаю, что я хочу сделать Doesnt существовать изначально, так что я просто создал словарь ошибок (обновляются в любое время изменения свойства) на объекте, и пусть возвращают ошибки CarriageReturn запятых списка ошибок, например, так:

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

Это приятно. Было бы лучше сохранить обновленную версию ErrorCollection с последним сообщением об ошибке (если ключ уже существует, а сообщение не является нулевым). – danjarvis

+0

Свойство 'Error' можно оптимизировать для:' get {return string.Join (Environment.NewLine, errorCollection.Values); } ', если он пуст, он возвращает только« ", который (как указано в документации для интерфейса) не рассматривается как ошибка. – Tatranskymedved

1

Я понимаю, что для использования этого интерфейса вы перечисляете свойства объекта и вызываете индексатор один раз для каждого свойства. Ответственность вызывающего абонента заключается в объединении любых сообщений об ошибках.

+0

Спасибо. Вы правы. Я искал решение, которое более инкапсулировано внутри бизнес-объекта, см. Выше. Возможно, это не самое совершенное решение для разделения проблем. – Bob

11

Я думаю, что гораздо проще использовать атрибуты Validation.

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

Вспомогательный класс InputValidation выглядит следующим образом

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

Это еще одно хорошее решение, спасибо. Это работало для меня по свойству VM, которое не было стандартным IDataErrorInfo. Если кто-либо использует это, необходимо добавить ссылку на System.ComponentModel.DataAnnotations. – Bob

+0

Это превосходно - из мира MVC здорово иметь возможность использовать ту же конвенцию. – Cody