Как определить свойство ошибки IDataErrorInfo для нескольких свойств BO - PullRequest
10 голосов
/ 21 января 2010

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

Спасибо!

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

Ответы [ 3 ]

11 голосов
/ 21 января 2010

Да, я вижу, где вы могли бы использовать индексатор. Думаю, неплохой путь. Я был действительно сосредоточен на свойстве «Ошибка». Мне нравится идея наличия ошибок, содержащихся в бизнес-объекте. Я думаю, что то, что я хочу сделать, не существует изначально, поэтому я просто создал словарь ошибок (обновляется каждый раз, когда изменяется свойство) объекта и позволяю Error возвращать список ошибок с разделением 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();
        }
    }
11 голосов
/ 25 января 2010

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

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 голос
/ 21 января 2010

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

...