【问题标题】:Using IDataErrorInfo with Nested Objects将 IDataErrorInfo 与嵌套对象一起使用
【发布时间】:2012-09-17 11:32:56
【问题描述】:

我正在使用 MVVM,我想使用 IDataErrorInfo 来验证我的视图。

我当前的实现包括嵌套对象和不同的 ViewModel。例如业务实体“客户”包含业务实体“地址”。我在我的视图中直接访问地址,例如“Customer.Address”。要验证 Address 中的更改,我必须在 Address 中实现 IDataErrorInfo。

我在不同的视图/视图模型中使用客户或地址。在不同的视图/视图模型中使用会导致不同的验证行为。因此,在实体本身中实现验证是不够的。

在 ViewModel 中直接公开我想要更改的属性(创建直接设置/获取实体的新属性)似乎使 ViewModel 过于僵化。而且太大了。

我不能从基类继承,因为一些业务实体已经从其他对象派生(这是我无法改变的事实)。 我目前看到的唯一选择是将 ViewModel 的接口添加到业务实体,并将业务实体中的 this[] 调用转发到该 ViewModel 接口。

是否有关于如何在 ViewModel 中验证这些嵌套对象的最佳实践?

编辑:验证我不认为业务对象中的验证是一个有用的想法的另一个原因是,我需要在我的 ViewModel 中使用不同的业务对象来验证视图和数据条目。

【问题讨论】:

    标签: c# mvvm idataerrorinfo


    【解决方案1】:

    我过去这样做的一种方法是expose a ValidationDelegate on the Model,它允许 ViewModel 将自己的验证代码附加到模型。

    通常我这样做是因为我使用Model 层作为普通数据对象,所以我的模型只验证基本的东西,例如最大长度或非空值,而任何不特定于数据模型的高级验证都在视图模型。这通常包括诸如确保项目是唯一的,或者用户有权将值设置为特定范围之类的事情,甚至像您的情况一样,验证仅针对特定操作存在。

    public class CustomerViewModel
    {
        // Keeping these generic to reduce code here, but it
        // should include PropertyChange notification
        public AddressModel Address { get; set; }
    
        public CustomerViewModel()
        {
            Address = new AddressModel();
            Address.AddValidationDelegate(ValidateAddress);
        }
    
        // Validation Delegate to validate Adderess
        private string ValidateAddress(object sender, string propertyName)
        {
            // Do your ViewModel-specific validation here.
            // sender is your AddressModel and propertyName 
            // is the property on the address getting validated
    
            // For example:
            if (propertyName == "Street1" && string.IsNullOrEmpty(Address.Street1))
                return "Street1 cannot be empty";
    
            return null;
        }
    }
    

    这是我通常用于验证委托的代码:

    #region IDataErrorInfo & Validation Members
    
    #region Validation Delegate
    
    public delegate string ValidationDelegate(
        object sender, string propertyName);
    
    private List<ValidationDelegate> _validationDelegates = 
        new List<ValidationDelegate>();
    
    public void AddValidationDelegate(ValidationDelegate func)
    {
        _validationDelegates.Add(func);
    }
    
    public void RemoveValidationDelegate(ValidationDelegate func)
    {
        if (_validationDelegates.Contains(func))
            _validationDelegates.Remove(func);
    }
    
    #endregion // Validation Delegate
    
    #region IDataErrorInfo for binding errors
    
    string IDataErrorInfo.Error { get { return null; } }
    
    string IDataErrorInfo.this[string propertyName]
    {
        get { return this.GetValidationError(propertyName); }
    }
    
    public string GetValidationError(string propertyName)
    {
        string s = null;
    
        foreach (var func in _validationDelegates)
        {
            s = func(this, propertyName);
            if (s != null)
                return s;
        }
    
        return s;
    }
    
    #endregion // IDataErrorInfo for binding errors
    
    #endregion // IDataErrorInfo & Validation Members
    

    【讨论】:

    • 所以“IDataErrorInfo & Validation Members”需要在每个业务实体中实现,如果我理解正确的话?由于我无法从任何 Base 对象继承,因此必须将其粘贴到每个实体中。
    • @Andreas 我一直在我的基本模型类中使用此代码,并在模型的构造函数中使用AddValidationDelegate(func) 附加验证。我想您也可以根据需要将其复制/粘贴到每个类中,并在 GetValidationError(propertyName) 方法中添加验证,但我认为在基类中实现这一点要好得多。
    • 一个基类会很好,是的,但是由于一些限制,我不能保证我能够在我的模型中使用一个基类。
    • @Andreas 我刚刚重新阅读了您的问题并意识到您说在许多情况下您不能从基类继承,所以是的,您必须在您的模型中复制/粘贴此代码并确保他们实施IDataErrorInfo。我想根据您现有的基类的另一种方法是首先扩展基类以添加验证,然后将您的模型基于扩展的基类而不是直接基类。比如CustomerModel可能继承自ValidatingCustomerBase,而ValidatingCustomerBase继承自CustomerBase
    • @Rachel 这是否可能与 INotifyDataErrorInfo 相关,如果可以,您能否指出正确的方向以获取更多信息,非常感谢。
    【解决方案2】:

    在不同的视图/视图模型中使用会导致不同的验证行为。

    因此,您有不同的视图模型。如果您不能从某些基本视图模型继承这些视图模型,请使用聚合:

    public class Address {}
    
    public class AddressViewModel1 : IDataErrorInfo
    {
      private readonly Address address;
      // other stuff here
    }
    
    public class AddressViewModel2 : IDataErrorInfo
    {
      private readonly Address address;
      // other stuff here
    }
    

    【讨论】:

    • 这对我来说不是一个有效的解决方案。这使得问题/ViewModel 比在 ViewModel 本身中公开所需的属性更加严格。澄清一下:在某些业务对象中,我们(可能)必须添加数百个这样的聚合类,这只是我不会做的事情。此外,我也不想(也可以)在每个 ViewModel 中使用不同的类。
    • @Andreas:为什么这是僵化的?您仍然可以像Customer.Address 这样从视图中使用 VM,但您可以编写不同的验证行为。
    • @Andreas:你有 BO,可以使用 100 个或更多不同的验证规则集进行验证?你能举一个那个物体的例子吗?
    • 好吧,我必须为每个 ViewModel 创建一个不同的 AddressViewModel 类,从长远来看,这很令人困惑。另外,我必须添加它来验证大多数对象,我需要 ViewModel 中的其他对象
    • 好吧,几百可能有点夸张,因为很多案例都可以通过参数展示出来。但是,这并没有改变我在之前的 cmets 中陈述的事实(并在原始帖子中进行了更新)。
    【解决方案3】:

    如何使用依赖注入并为每个不同的视图模型向客户对象注入验证服务?

    但我认为在您的视图模型中实现 idataerrorinfo 和所有需要的属性会更干净,但当然要多工作一次。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-05-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多