【问题标题】:C# validation: IDataErrorInfo without hard-coded strings of property name?C# 验证:没有硬编码的属性名称字符串的 IDataErrorInfo?
【发布时间】:2011-05-21 23:45:53
【问题描述】:

实施IDataErrorInfo 的最佳做法是什么?无论如何,如果没有属性名称的硬编码字符串来实现它吗?

【问题讨论】:

  • 此接口用于 Windows 窗体数据绑定。在那里获取属性名称(实际上是列)并不是什么大问题。您确定要在 WPF 项目中使用它吗?
  • @Hans:是的,我认为人们也将它用于 WPF:stackoverflow.com/questions/63646/…

标签: c# wpf validation


【解决方案1】:

通用验证例程的基类

如果您在 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 属性级别的“必需”或“最大长度”之类的琐碎内容到不同 UI 级别的“仅当检查其他内容时才需要”以及所有直到域/持久性级别中的“用户名不存在”。您会发现您必须在 UI 中重复一些验证逻辑与在域中添加大量元数据以向 UI 描述自身之间进行权衡,并且您必须权衡这些向用户显示不同类别的验证错误。

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

【讨论】:

  • 非常感谢。一个不相关的问题:您能否给我一些关于我们为什么需要SynchronizationContext 以及应该如何使用它的背景信息?我对并发编程不是很熟悉,但对此很好奇。 :)
  • WPF 和 Windows 窗体控件和元素不是线程安全的。这意味着当您与它们交互时,您需要在创建它们的同一线程上,即带有消息泵的线程。如果您在后台线程中触发以执行某些工作,然后触摸视图模型的这些 INotifyPropertyChanged 属性之一,则该事件将触发并且控件将由于错误线程上的事件而尝试更新自身。 SynchronizationContext 的 Windows 窗体实现允许我们将该回调编组到正确的线程。如果感到困惑,请忽略它。
  • 对于 WPF,您应该使用 Dispatcher 而不是 SynchronizationContext。
  • +1 用于使用“胖手指”。我完全要把它添加到我的基类中!
【解决方案2】:

您可能会在我的问题Select a model property using a lambda and not a string property name 的已接受答案中找到一些用处,专门用于在不使用字符串的情况下指定属性。恐怕我无法直接帮助实现IDataErrorInfo

【讨论】:

    【解决方案3】:

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

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

    仍有一些重复,但在一些项目中对我来说效果很好。

    至于组织验证...您可以考虑在运行时提供一个单独的 CustomerValidator 类。然后,您可以为不同的上下文交换不同的实现。因此,例如,新客户的验证方式与现有客户的验证方式不同。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-02-10
      • 2015-09-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-09
      相关资源
      最近更新 更多