【问题标题】:How can I define a IDataErrorInfo Error Property for multiple BO properties如何为多个 BO 属性定义 IDataErrorInfo 错误属性
【发布时间】:2011-01-07 21:38:57
【问题描述】:

我开始通过 IDataErrorInfo 接口在我的 WPF 项目中实现验证。 我的业务对象包含多个带有验证信息的属性。如何获取与对象关联的所有错误消息的列表。我的想法是这就是 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 ;
        }
    }

【问题讨论】:

    标签: c# wpf mvvm idataerrorinfo


    【解决方案1】:

    是的,我知道你可以在哪里使用索引器。我想这不是一个糟糕的方式。不过,我真的专注于“错误”属性。我喜欢将错误包含在业务对象中的概念。我认为我想要做的事情本身并不存在,所以我只是在对象上创建了一个错误字典(在属性更改时更新)并让错误返回一个以 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();
            }
        }
    

    【讨论】:

    • 这很好。最好使用最新的错误消息使 errorCollection 保持最新(如果键已经存在,并且消息不为空)。
    • Error 属性可以优化为:get { return string.Join(Environment.NewLine, errorCollection.Values); },如果为空,则只返回“”,即(如接口文档中所写)不视为错误。跨度>
    【解决方案2】:

    我认为使用验证属性要容易得多。

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

    【讨论】:

    • 这是另一个很好的解决方案,谢谢。它适用于标准 IDataErrorInfo 没有的 VM 属性。如果有人使用这个,需要添加对 System.ComponentModel.DataAnnotations 的引用。
    • 这太棒了——来自 MVC 世界,能够使用相同的约定真是太好了。
    【解决方案3】:

    我的理解是,要使用这个接口,你要枚举对象上的属性,并为每个属性调用一次索引器。汇总任何错误消息是调用者的责任。

    【讨论】:

    • 谢谢。你是对的。我一直在寻找一种更封装在业务对象中的解决方案,见上文。也许不是关注点分离的最完美解决方案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多