【问题标题】:MVVM - ValidationMVVM - 验证
【发布时间】:2011-05-08 07:56:12
【问题描述】:

我们试图找出 mvvm 中的验证,在业务逻辑或模型中进行验证。 我已经在我们的业务逻辑中实现了通过异常类型验证 - 可以在此处找到简化图:

如果我们有很多相互独立的输入,则没有问题,抛出异常,文本框捕获它并标记每个错误输入的边界为红色。但是,当我们有依赖值时,我们就有麻烦了。 例如

  • 模型中的 Value1 和 Value2 不能相同,因此我们在每个寻找相等值的人中都有一个验证函数,如果发生这种情况会抛出异常

  • 现在,如果我们将 Value1 设置为 0,将 Value2 设置为 1,一切都很好

  • Value1 在 GUI 中设置为 1 --> 这个被标记为红色,因为没有触发其他值的验证,所以 GUI 中的 Value2 没有标记为故障

  • Value2 在 GUI 中设置为 2,现在我们已达到有效状态,但只有 Value2 得到验证,因此 Value1 仍被标记为故障

是否有解决该问题的通用模式?我们不想在两个文本框之间的 GUI 中引入依赖关系,因为这个逻辑应该只存在于业务逻辑层中。

除了实现通过异常验证之外,还可以实现 IDataErrorInfo 接口,但问题仍然存在,没有办法强制依赖值再次验证它们的值,至少我看不到 :)

感谢任何帮助

干杯, 曼尼


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


15.11.2010 - 第 2 部分

好的,在这里重新考虑一下,我们将使用业务逻辑层。这是我们目前计划的配置: (这里的图片有点小,请在单独的窗口中打开它以全尺寸显示) 一切都或多或少清楚,除了如果业务逻辑下的数据模型发生更改,如何通知不同编辑器的所有视图模型/模型克隆。一种方法是在创建它们的业务逻辑中跟踪克隆模型。当使用业务逻辑 commit() 更改数据模型时,所有其他已注册的模型克隆都可以收到更改通知并进一步传播它们。或者,业务逻辑可以发布一个所有视图模型都订阅的事件,以便他们也获得更改 - 谁能给我提示什么更好?

再次感谢您的帮助,对不起,我的思维如此受阻;)

【问题讨论】:

  • 为什么VM在业务层设置属性值?这似乎是您的某些问题的根本原因。每个人(似乎)对 MVVM 的含义略有不同的解释,但恕我直言,该模型是数据模型和控制器的组合,因此它拥有 VM 中数据的超集并协调对 Web 服务/存储库/等的访问.因此,您现有的业务层应该合并到当前模型中,或者可能移到模型之后(即在 WCF 边界的另一侧)。
  • 我们正在考虑将模型(这是我们的数据模型)和业务逻辑折叠成一层,如果我理解正确的话,这基本上就是您的意思。但我仍然认为 vm 不应该包含验证。但是字符串传递问题仍然存在。当将业务逻辑移动到 wcf 边界的另一侧时,这是否意味着所有模型都只是一个可以编辑并作为一个整体提交给业务逻辑并在那里评估的哑数据持有者?你有关于使用 wcf 边界和拆分的链接吗?感谢您的帮助
  • 当然是在虚拟机中进行验证,但它是简单的验证,例如密码超过 6 个字符电子邮件地址是有效格式。您可能希望将业务逻辑与模型分离,考虑对模型->业务逻辑->数据存储库使用 n 层方法(将 BL 和数据层合并到 Web 服务中很常见,但是如果您只是使用本地数据库,则不必如此)。在模型中加入 BL 仍然是比您目前拥有的更好的选择。
  • @slugster:你对 vm 中的简单验证完全正确,我应该更清楚地说明这一点。我已经更新了上面的帖子,遗憾的是无法在此处创建讨论帖。非常感谢,感谢您对我的耐心;)
  • 您似乎担心在 VM 中进行验证,这是一个非常合理的问题。 VM 是视图和模型之间的中介。为什么不使用 IDataErrorInfo 将视图通过视图模型的验证委托给模型?将验证放入您的模型中,然后从 VM 中调用该验证?就像客户端 javascript 对服务器执行 XHR 以进行验证一样,所以它都包含在一个地方。

标签: c# validation mvvm business-logic


【解决方案1】:

您可以考虑使用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 标记(特别注意 Text 绑定中的 ValidatesOnDataErrorsValidatesOnExceptions 属性):

<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"  
         />

【讨论】:

  • 感谢您的详细回答。它与异常方法完全不同,因为使用 IDataErrorInfo 接口,您基本上 - 设置值 - 检查对象是否处于有效状态,这意味着在两者之间您会得到一个状态无效的对象,如果您有这很好提交/保存按钮,并且正在处理模型的克隆,但是直接进入具有无效状态的模型有点危险
  • @user 如果验证失败,此示例中的数据将永远不会命中视图模型。绑定将首先验证,然后在绑定到视图模型之前拒绝数据。
  • @josh:由于 this[string] 方法采用属性名称并使用该名称访问它自己的成员,因此该成员必须具有新的无效值,否则无法进行检查(并且会变成旧值)-或者我在这里遗漏了什么?
  • 我不喜欢代码中没有太多的字符串,感觉如果我每次重命名一个属性都会在运行时中断
  • @Manni,你是对的,对不起。但是您将使用来自视图模型的验证来决定您是否进一步使用该属性。 @Ian 在常见的 mvvm 框架中,许多属性名称已经包含在绑定到私有成员的字符串中,以引发 propertychanged 事件。您也可以利用这些名称进行验证。然后当一个属性名称改变时,你只需将赋值更改为私有变量,绑定+验证继续工作。
【解决方案2】:

是否有解决该问题的通用模式?我们不想在两个文本框之间的 GUI 中引入依赖关系,因为这个逻辑应该只存在于业务逻辑层中。

  1. Value1Value2相互依赖,因为“模型中的Value1和Value2不能相同”的条件。

  2. 这意味着当Value2 改变时,Value1 也会改变,反之亦然!实际上,当Value2发生变化时,Value1验证结果也会发生变化,但这与前一种说法很接近。

  3. Value1Value2 设置器必须通知 Value1Value2 属性更改。

  4. 视图必须重新读取和重新验证这两个值并清除错误标记。

  5. 如果 WPF 发现通知事件已引发但值实际上没有改变,不确定是否会这样做。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-12-06
    • 2017-09-02
    • 1970-01-01
    • 1970-01-01
    • 2014-01-29
    • 2010-12-25
    • 2015-04-21
    • 1970-01-01
    相关资源
    最近更新 更多