MVVM - 验证

17
我们正在尝试在mvvm中解决验证问题,即在业务逻辑或模型中进行验证。 我已经在我们的业务逻辑中实现了异常类型的验证-简化的图表可以在这里找到: alt text 如果我们有很多相互独立的输入,那么没有问题,异常会被抛出,文本框会将其边框标记为红色以表示每个错误的输入。然而,当我们有依赖值时,我们就有麻烦了。 例如:
- 模型中的Value1和Value2不能相同,因此我们在每个值中都有一个验证函数,查找相等的值并在发生异常时抛出异常 - 现在,如果我们将Value1设置为0并将Value2设置为1,一切都很好 - 在GUI中将Value1设置为1->这个值被标记为红色,因为其他值的验证没有触发,因此GUI中的Value2没有标记为错误 - 在GUI中将Value2设置为2,现在我们已经达到了有效状态,但只有Value2得到了验证,所以Value1仍然被标记为错误
是否有一种常见的模式来解决这个问题?我们不想在两个文本框之间引入GUI中的依赖关系,因为这个逻辑应该只存在于业务逻辑层中。
除了实现异常验证之外,还可以实现IDataErrorInfo接口,但问题仍然存在,没有办法强制依赖值重新验证其值,至少我看不到:)
感谢任何帮助。

干杯,manni


[清理,删除不必要的步骤]


2010年11月15日 - 第二部分

好的,我们在这里进行了大量的思考,我们将使用业务逻辑层。以下是我们当前计划的配置: alt text (这张图片在这里有点小,请在单独的窗口中打开以显示完整大小) 除了如何通知不同编辑器的所有视图模型/模型克隆数据模型下的业务逻辑发生更改之外,其他一切都比较清晰。一种方法是在创建它们的业务逻辑中跟踪克隆的模型。当使用业务逻辑commit()更改数据模型时,所有其他已注册的模型克隆都可以被通知到更改并进一步传播它们。或者,业务逻辑可以发布一个事件,所有视图模型都订阅该事件,以便他们也可以获得更改-有人能给我一个提示哪个更好吗?

再次感谢您的帮助,对不起我很困惑;)


为什么VM在业务层中设置属性值?这似乎是一些问题的根本原因。每个人(似乎)对MVVM有稍微不同的解释,但在我看来,模型是数据模型和控制器的组合,因此它持有VM中数据的超集,并协调访问Web服务/存储库等。因此,您现有的业务层应该被合并到当前模型中,或者可能移动到模型之后(即WCF边界的另一侧)。 - slugster
我们正在考虑将模型(即我们的数据模型)和业务逻辑合并为一个层,如果我理解正确,这基本上就是您的意思。但我仍然认为vm不应该包含验证。但字符串传递问题仍然存在。 当将业务逻辑移动到wcf边界的另一侧时,是否意味着整个模型只是一个可以编辑并作为整体提交给业务逻辑并在那里进行评估的愚蠢数据持有者? 您是否有关于使用wcf边界和拆分的任何链接? 感谢您的帮助。 - manni
在虚拟机中进行验证,但它是简单的验证,例如“密码长度超过6个字符”或“电子邮件地址格式正确”。您可能希望将业务逻辑与模型解耦,考虑使用n层方法进行模型->业务逻辑->数据存储库(通常将BL和数据层合并为Web服务,但如果您只是使用本地数据库,则不必如此)。将BL放在模型中仍然比您当前的选项更好。 - slugster
@slugster:您在VM中进行简单验证是完全正确的,我应该更清楚地表明这一点。我已经更新了上面的帖子,在此没有创建讨论线程的可能性是遗憾的。非常感谢,我很感激您对我的耐心 ;) - manni
您似乎关心在VM中进行验证,这是非常合理的。VM是视图和模型之间的中介。为什么不使用IDataErrorInfo将验证从视图委托给视图模型,再由视图模型调用模型中的验证呢?就像客户端JavaScript向服务器进行XHR验证一样,这样所有内容都包含在一个地方。将验证放在您的模型中,并从VM调用该验证如何? - Josh Smeaton
你说得对。我认为我们会采用2010年11月15日第二部分中的图示方法。在虚拟机中进行简单验证(长度,空值等),并在业务逻辑中进行相关验证。现在唯一的问题是如何传播模型变化,如果在多个编辑器中打开它。跟踪克隆模型(通过从业务逻辑请求)并通知它们底层模型已更改是目前的主要任务。有两种可能性,一种是通知各自的克隆模型,另一种是使用全局事件总线,你认为哪种方式更好? - manni
2个回答

15
您可以考虑使用System.ComponentModel.IDataErrorInfo接口。这个非常方便的接口使您能够:
  • 以符合MVVM的方式进行验证
  • 为任何特定字段进行自定义验证(如果需要,验证可以检查多个值)
  • 将UI绑定到验证错误
您在视图模型上实现IDataErrorInfo(甚至在视图模型基类中虚拟实现它,并在派生的视图模型中重写它)。由于数据绑定的性质,我需要检查的值都在视图模型中,我可以测试它们的任何组合。当然,您仍然需要在业务层中进行验证,但您不再需要访问业务层(或模型)来进行一些验证。
以下是从收集一些用户详细信息并对其进行基本验证的(WPF)屏幕的快速示例:
C# 代码:
    #region IDataErrorInfo Members

    /// <summary>
    /// Gets an error message indicating what is wrong with this object.
    /// </summary>
    /// <value></value>
    /// <returns>An error message indicating what is wrong with this object. The default is an empty string ("").</returns>
    public override string Error
    {
        get
        {
            return this["UserCode"] + this["UserName"] + this["Password"] + this["ConfirmedPassword"] + this["EmailAddress"];
        }
    }

    /// <summary>
    /// Gets the <see cref="System.String"/> with the specified column name.
    /// </summary>
    /// <value></value>
    public override string this[string columnName]
    {
        get
        {
            switch (columnName)
            {
                case "UserCode":
                    if (!string.IsNullOrEmpty(UserCode) && UserCode.Length > 20)
                        return "User Code must be less than or equal to 20 characters";
                    break;

                case "UserName":
                    if (!string.IsNullOrEmpty(UserCode) && UserCode.Length > 60)
                        return "User Name must be less than or equal to 60 characters";
                    break;

                case "Password":
                    if (!string.IsNullOrEmpty(Password) && Password.Length > 60)
                        return "Password must be less than or equal to 60 characters";
                    break;

                case "ConfirmedPassword":
                    if (Password != ConfirmedPassword)
                        return Properties.Resources.ErrorMessage_Password_ConfirmedPasswordDoesntMatch; 
                    break;

                case "EmailAddress":
                    if (!string.IsNullOrEmpty(EmailAddress))
                    {
                        var r = new Regex(_emailRegex);
                        if (!r.IsMatch(EmailAddress))
                            return Properties.Resources.ErrorMessage_Email_InvalidEmailFormat;
                    }
                    break;
            }
            return string.Empty;
        }
    }

    #endregion

以下是页面上两个文本框的XAML标记(特别注意

<TextBox Name="UserCodeTextBox" 
         Text="{Binding UserCode, 
                Mode=TwoWay, 
                UpdateSourceTrigger=PropertyChanged, 
                ValidatesOnDataErrors=True, 
                ValidatesOnExceptions=True, 
                NotifyOnSourceUpdated=True, 
                NotifyOnTargetUpdated=True}" 
         GotFocus="Input_GotFocus"
         VerticalAlignment="Top"
         Margin="165,0,150,0"  
         CharacterCasing="Upper"
         />

<TextBox Name="UserNameTextBox" 
         Text="{Binding UserName, 
                Mode=TwoWay, 
                UpdateSourceTrigger=PropertyChanged, 
                ValidatesOnDataErrors=True, 
                ValidatesOnExceptions=True, 
                NotifyOnSourceUpdated=True, 
                NotifyOnTargetUpdated=True}" 
         GotFocus="Input_GotFocus"
         VerticalAlignment="Top"
         Margin="165,30,0,0"  
         />

1
@josh:由于此行代码中的this[string]方法获取属性名称并访问具有该名称的自身成员,因此该成员必须具有新的无效值,否则无法进行检查(并且将对旧值进行检查)-或者我在这里漏掉了什么? - manni
2
我不喜欢代码中有太多的字符串,感觉如果我重命名一个属性,它就会在运行时全部崩溃。 - Ian Ringrose
@Manni,你是对的,抱歉。但你会使用视图模型中的验证来决定是否继续处理该属性。 @Ian,在常见的MVVM框架中,许多属性名称已经包含在绑定到私有成员的字符串中,以引发propertychanged事件。您还可以利用这些名称进行验证。然后,当属性名称更改时,您只需更改对私有变量的赋值,绑定和验证就会继续工作。 - Josh Smeaton
@josh:对于我们来说,魔术字符串仍然是一个问题,特别是在重构时。我们通过使用PostSharp和在后编译过程中自动创建PropertyChanged(string)事件来解决了属性更改事件的问题。也许我们可以利用类似的方法来处理验证过程。 - manni
@josh:另外,我们不想在视图模型中进行验证,业务逻辑验证的位置应完全在业务逻辑层(或模型)中进行。可以将验证事件向下传递,但需要额外的魔术字符串。我会将我们当前的类图作为答案发布,因为我无法在评论中完成。谢谢您的建议,祝好! - manni
显示剩余2条评论

0
这个问题有通用的解决方案吗?我们不想在 GUI 中引入两个文本框之间的依赖关系,因为这个逻辑只应该存在于业务逻辑层中。
1.由于模型中的“Value1和Value2不能相同”条件,Value1和Value2是相互依赖的。 2.这意味着当Value2更改时,Value1也会更改,反之亦然!实际上,当Value2更改时,Value1的验证结果会更改,但这与前面的语句接近。 3.Value1和Value2的setter必须通知关于Value1和Value2属性的更改。 4.视图必须重新读取和重新验证两个值,并清除错误标记。 5.不确定WPF是否会这样做,如果它发现通知事件已经被触发但实际的值并没有更改。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接