C#验证:没有硬编码的属性名字符串的IDataErrorInfo?

时间:2010-12-08 02:50:04

标签: c# wpf validation

实施IDataErrorInfo的最佳做法是什么?反正有没有硬编码的字符串属性名称来实现它?

3 个答案:

答案 0 :(得分:13)

常见验证例程的基类

如果您在IDataErrorInfo实施中进行了一些预测,则可以使用DataAnnotations。例如,这是我经常使用的基本视图模型(从Windows窗体,但您可以推断):

public class ViewModelBase : IDataErrorInfo, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public SynchronizationContext Context
    {
        get;
        set;
    }

    public bool HasErrors
    {
        get
        {
            return !string.IsNullOrWhiteSpace(this.Error);
        }
    }

    public string Error
    {
        get 
        {
            var type = this.GetType();
            var modelClassProperties = TypeDescriptor
                .GetProperties(type)
                .Cast();

            return
                (from modelProp in modelClassProperties
                    let error = this[modelProp.Name]
                    where !string.IsNullOrWhiteSpace(error)
                    select error)
                    .Aggregate(new StringBuilder(), (acc, next) => acc.Append(" ").Append(next))
                    .ToString();
        }
    }

    public virtual string this[string columnName]
    {
        get
        {
            var type = this.GetType();
            var modelClassProperties = TypeDescriptor
                .GetProperties(type)
                .Cast();

            var errorText =
                (from modelProp in modelClassProperties
                    where modelProp.Name == columnName
                    from attribute in modelProp.Attributes.OfType()
                    from displayNameAttribute in modelProp.Attributes.OfType()
                    where !attribute.IsValid(modelProp.GetValue(this))
                    select attribute.FormatErrorMessage(displayNameAttribute == null ? modelProp.Name : displayNameAttribute.DisplayName))
                    .FirstOrDefault();

            return errorText;
        }
    }

    protected void NotifyPropertyChanged(string propertyName)
    {
        if (string.IsNullOrWhiteSpace(propertyName))
        {
            throw new ArgumentNullException("propertyName");
        }

        if (!this.GetType().GetProperties().Any(x => x.Name == propertyName))
        {
            throw new ArgumentException(
                "The property name does not exist in this type.",
                "propertyName");
        }

        var handler = this.PropertyChanged;
        if (handler != null)
        {
            if (this.Context != null)
            {
                this.Context.Post(obj => handler(this, new PropertyChangedEventArgs(propertyName)), null);
            }
            else
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

示例用法:

public class LogOnViewModel : ViewModelBase
{
    [DisplayName("User Name")]
    [Required]
    [MailAddress] // This is a custom DataAnnotation I wrote
    public string UserName
    {
        get
        {
                return this.userName;
        }
        set
        {
                this.userName = value;
                this.NotifyPropertyChanged("UserName");
        }
    }

    [DisplayName("Password")]
    [Required]
    public string Password
    {
        get; // etc
        set; // etc
    }
}

利用IDataErrorInfo进行一次性验证例程

说实话,我最终同时使用注释和开关。我使用注释进行简单验证,如果我有更复杂的注释(例如“如果设置了其他属性,则仅验证此属性”),那么我将在覆盖this[]的情况下使用该开关指数。这种模式经常看起来像这样(只是一个例子,它没有意义:

public override string this[string columnName]
{
    get
    {
        // Let the base implementation run the DataAnnotations validators
        var error = base[columnName];

        // If no error reported, run my custom one-off validations for this
        // view model here
        if (string.IsNullOrWhiteSpace(error))
        {
            switch (columnName)
            {
                case "Password":
                    if (this.Password == "password")
                    {
                        error = "See an administrator before you can log in.";
                    }

                    break;
            }
        }

        return error;
    }

一些意见

至于将属性名称指定为字符串:你可以用lambdas做一些奇特的事情,但我诚实的建议是只是克服它。您可能会注意到,在我的ViewModelBase中,我的小NotifyPropertyChanged帮助器会做一些反射魔法,以确保我没有胖指的属性名称 - 这有助于我快速检测数据绑定错误而不是跑了20分钟弄清楚我错过了什么。

您的应用程序将进行一系列验证,从UI属性级别的“必需”或“最大长度”等“piddly”到“在其他UI级别检查其他内容时仅需要”并且所有在域/持久性级别中,“用户名不存在”的方式。您会发现,在UI中重复一点验证逻辑与在域中添加大量元数据以向UI描述自身之间,您将不得不进行权衡,并且您将不得不权衡这些如何向用户显示不同类别的验证错误。

希望有所帮助。祝你好运!

答案 1 :(得分:2)

您可以在我的问题Select a model property using a lambda and not a string property name的已接受答案中找到一些用途,特别是仅用于指定不使用字符串的属性。我担心我无法直接帮助实施IDataErrorInfo

答案 2 :(得分:0)

对于这种情况(和INotifyPropertyChanged),我倾向于使用私有静态类,将所有属性名称声明为常量:

public class Customer : INotifyPropertyChanging, INotifyPropertyChanged, IDataErrorInfo, etc
{
  private static class Properties
  {
    public const string Email = "Email";
    public const string FirstName = "FirstName";
  }  


}

还有一点点重复,但在一些项目上它对我来说很好。

至于组织验证......您可以考虑在运行时提供单独的CustomerValidator类。然后,您可以为不同的上下文交换不同的实现。因此,例如,新客户可以与现有客户进行不同的验证,而不会出现条件问题。