【问题标题】:Using dataAnnonation regular expression validation attribute in Wpf Mvvm IdataErrorInfo在 Wpf Mvvm IdataErrorInfo 中使用 dataAnnonation 正则表达式验证属性
【发布时间】:2017-03-31 05:48:11
【问题描述】:

我是使用 mvvm 开发 wpf 应用程序的新手。因此,如果我问的是开箱即​​用的问题,请忽略。我有一个模型类,我在其中使用数据注释验证数据。

这是模型类的代码部分

/// <summary>
    /// The firstname of the person.
    /// </summary>
    [Required(AllowEmptyStrings = false, ErrorMessage = "First name must not be empty.")]
    [MaxLength(20, ErrorMessage = "Maximum of 20 characters is allowed.")]
    public string Firstname { get; set; }

    /// <summary>
    /// The lastname of the person.
    /// </summary>
    [Required(AllowEmptyStrings = false, ErrorMessage = "Address must not be empt.")]
    public string Address { get; set; }

    [MaxLength(20, ErrorMessage = "Maximum of 20 characters is allowed.")]
    public string PhoneNum { get; set; }

我的验证完全绑定到 xaml,工作正常,并且在“必需和最大长度属性”的情况下在文本框中显示错误。现在我想在模型类中使用 Regular Expression Attribute 和我的电话号码。喜欢

[RegularExpression("^[0-9]*$", ErrorMessage = "Phone Num must be numeric")]
[MaxLength(20, ErrorMessage = "Maximum of 20 characters is allowed.")]
public string PhoneNum { get; set; }

这是我的 BaseModel 类中 IDataErrorInfo 的代码。

using Annotations;

/// <summary>
/// Abstract base class for all models.
/// </summary>
public abstract class BaseModel : INotifyPropertyChanged, IDataErrorInfo
{
    #region constants

    private static List<PropertyInfo> _propertyInfos;

    #endregion

    #region events

    /// <summary>
    /// Occurs when a property value changes.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    #endregion

    #region constructors and destructors

    /// <summary>
    /// Default constructor.
    /// </summary>
    public BaseModel()
    {
        InitCommands();
    }

    #endregion

    #region explicit interfaces

    /// <summary>
    /// Gets an error message indicating what is wrong with this object.
    /// </summary>
    /// <returns>
    /// An error message indicating what is wrong with this object. The default is an empty string ("").
    /// </returns>
    public string Error => string.Empty;

    /// <summary>
    /// Gets the error message for the property with the given name.
    /// </summary>
    /// <returns>
    /// The error message for the property. The default is an empty string ("").
    /// </returns>
    /// <param name="columnName">The name of the property whose error message to get. </param>
    public string this[string columnName]
    {
        get
        {
            CollectErrors();
            return Errors.ContainsKey(columnName) ? Errors[columnName] : string.Empty;
        }
    }

    #endregion

    #region methods

    /// <summary>
    /// Override this method in derived types to initialize command logic.
    /// </summary>
    protected virtual void InitCommands()
    {
    }

    /// <summary>
    /// Can be overridden by derived types to react on the finisihing of error-collections.
    /// </summary>
    protected virtual void OnErrorsCollected()
    {
    }

    /// <summary>
    /// Raises the <see cref="PropertyChanged" /> event.
    /// </summary>
    /// <param name="propertyName">The name of the property which value has changed.</param>
    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    /// <summary>
    /// Is called by the indexer to collect all errors and not only the one for a special field.
    /// </summary>
    /// <remarks>
    /// Because <see cref="HasErrors" /> depends on the <see cref="Errors" /> dictionary this
    /// ensures that controls like buttons can switch their state accordingly.
    /// </remarks>
    private void CollectErrors()
    {
        Errors.Clear();
        PropertyInfos.ForEach(
            prop =>
            {
                var currentValue = prop.GetValue(this);
                var requiredAttr = prop.GetCustomAttribute<RequiredAttribute>();
                var maxLenAttr = prop.GetCustomAttribute<MaxLengthAttribute>();

                if (requiredAttr != null)
                {
                    if (string.IsNullOrEmpty(currentValue?.ToString() ?? string.Empty))
                    {
                        Errors.Add(prop.Name, requiredAttr.ErrorMessage);
                    }
                }
                if (maxLenAttr != null)
                {
                    if ((currentValue?.ToString() ?? string.Empty).Length > maxLenAttr.Length)
                    {
                        Errors.Add(prop.Name, maxLenAttr.ErrorMessage);
                    }
                }


                // further attributes
            });
        // we have to this because the Dictionary does not implement INotifyPropertyChanged            
        OnPropertyChanged(nameof(HasErrors));
        OnPropertyChanged(nameof(IsOk));
        // commands do not recognize property changes automatically
        OnErrorsCollected();
    }

    #endregion

    #region properties

    /// <summary>
    /// Indicates whether this instance has any errors.
    /// </summary>
    public bool HasErrors => Errors.Any();

    /// <summary>
    /// The opposite of <see cref="HasErrors" />.
    /// </summary>
    /// <remarks>
    /// Exists for convenient binding only.
    /// </remarks>
    public bool IsOk => !HasErrors;

    /// <summary>
    /// Retrieves a list of all properties with attributes required for <see cref="IDataErrorInfo" /> automation.
    /// </summary>
    protected List<PropertyInfo> PropertyInfos
    {
        get
        {
            return _propertyInfos
                   ?? (_propertyInfos =
                       GetType()
                           .GetProperties(BindingFlags.Public | BindingFlags.Instance)
                           .Where(prop => prop.IsDefined(typeof(RequiredAttribute), true) || prop.IsDefined(typeof(MaxLengthAttribute), true))
                           .ToList());
        }
    }

    /// <summary>
    /// A dictionary of current errors with the name of the error-field as the key and the error
    /// text as the value.
    /// </summary>
    private Dictionary<string, string> Errors { get; } = new Dictionary<string, string>();

    #endregion
}

}

如何在我的 Basemodel 类中添加正则表达式属性?任何帮助将不胜感激。谢谢

【问题讨论】:

    标签: c# .net wpf validation mvvm


    【解决方案1】:

    而不是添加更多 or 子句 - || - 到您的属性中,您可以获取从 ValidationAttribute 派生的所有属性。所有 DataAnnotation 属性都派生自该类:

    /// <summary>
    /// Retrieves a list of all properties with attributes required for <see cref="IDataErrorInfo" /> automation.
    /// </summary>
    protected List<PropertyInfo> PropertyInfos
    {
        get
        {
            return _propertyInfos
                   ?? (_propertyInfos =
                       GetType()
                           .GetProperties(BindingFlags.Public | BindingFlags.Instance)
                           .Where(prop => prop.IsDefined(typeof(ValidationAttribute), true))
                           .ToList());
        }
    }
    

    如果您不喜欢这种方法,您可以添加 ||您要处理的每种属性类型的子句:

    protected List<PropertyInfo> PropertyInfos
    {
        get
        {
            return _propertyInfos
                    ?? (_propertyInfos =
                        GetType()
                            .GetProperties(BindingFlags.Public | BindingFlags.Instance)
                            .Where(prop => 
                                prop.IsDefined(typeof(RequiredAttribute), true) || 
                                prop.IsDefined(typeof(MaxLengthAttribute), true) ||
                                prop.IsDefined(typeof(RegularExpressionAttribute), true) )
                            .ToList());
        }
    }
    

    根据您的评论,我认为您需要一种通用的方法来验证您的属性,否则您的 CollectErrors 方法很快就会变得丑陋。

    尝试从我使用 Prism 开发的一个项目中采用的这种方法。此代码应进入您的 BaseModel 类。

    private bool TryValidateProperty(PropertyInfo propertyInfo, List<string> propertyErrors)
    {
        var results = new List<ValidationResult>();
        var context = new ValidationContext(this) { MemberName = propertyInfo.Name };
        var propertyValue = propertyInfo.GetValue(this);
    
        // Validate the property
        var isValid = Validator.TryValidateProperty(propertyValue, context, results);
    
        if (results.Any()) { propertyErrors.AddRange(results.Select(c => c.ErrorMessage)); }
    
        return isValid;
    }
    
    /// <summary>
    /// Is called by the indexer to collect all errors and not only the one for a special field.
    /// </summary>
    /// <remarks>
    /// Because <see cref="HasErrors" /> depends on the <see cref="Errors" /> dictionary this
    /// ensures that controls like buttons can switch their state accordingly.
    /// </remarks>
    private void CollectErrors()
    {
        Errors.Clear();
        PropertyInfos.ForEach(
            prop =>
            {
                //Validate generically
                var errors = new List<string>();
                var isValid = TryValidateProperty(prop, errors);
                if (!isValid)
                    //As you're using a dictionary to store the errors and the key is the name of the property, then add only the first error encountered. 
                    Errors.Add(prop.Name, errors.First());
            });
        // we have to this because the Dictionary does not implement INotifyPropertyChanged            
        OnPropertyChanged(nameof(HasErrors));
        OnPropertyChanged(nameof(IsOk));
        // commands do not recognize property changes automatically
        OnErrorsCollected();
    }
    

    【讨论】:

    • 是的,但是在正则表达式的情况下我将如何收集错误,就像我在 required 和 maxlength 属性的情况下所做的一样
    • @AmadZafar,我已经编辑了我的答案。我希望它能让你走上正轨。让我知道它是如何为您工作的。
    • 我在 Error.AddRange 中遇到错误,其中 Errors 是我定义的字典 private Dictionary Errors { get; } = 新字典();
    • @AmadZafar,因为您使用字典来存储错误并且键是属性名称,所以只存储遇到的第一个错误。见编辑。我会建议您根据需要进行验证。
    • 我已经做到了,先生。它就像一个魅力。非常感谢您的帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-08
    • 1970-01-01
    • 2021-02-08
    • 1970-01-01
    • 1970-01-01
    • 2018-09-08
    相关资源
    最近更新 更多