WPF、MVVM、带有验证的业务对象不太匹配

use*_*263 5 wpf business-rules mvvm idataerrorinfo

对于我的 WPF 应用程序,我决定使用 MVVM。这是我将如何实现这种模式的概念。

  • 我的模型(业务对象)负责验证(这对我来说是必须的)。
  • ViewModels 负责为友好的用户交互和一些安全方面包装我的模型。

我的第一个问题是关于在 ViewModel 中包装或不包装我的模型。

  • 当我不将我的模型包装在 ViewModel 中并将模型直接暴露给视图时 – 那么我不明白为什么我需要一个 ViewModel(这似乎毫无意义)
  • ViewModel 出于各种原因应该包装模型:

    1. 我不喜欢直接绑定到 Model (DateTime, int, ...) 中的强类型属性,因为当我这样做时 => WPF 会控制我对这些类型的验证。这真的很糟糕,因为当用户在 Datepicker 中写入 'aaaa' 时,我的模型是有效的(我的模型从不知道这一点,因为 WPF 控制强类型属性)并且启用了保存按钮 - 这真的是错误的。

    2. 我没有向视图公开我的模型的所有属性,我ViewModel应该保护我的模型(我有一些属性,应该在表示层只有 getter 而没有 setter)

我的决定是ViewModel绝对应该包装模型。所以ViewModel工具INotifyPropertyChanged

但是现在我在业务验证方面遇到了问题。

当我使用漂亮的 IDataErrorInfo 时,我在 ViewModel 中拥有整个业务规则,这打破了我的概念。业务规则绝对应该在模型中。

示例:当用户选择类型 A 时,则字段 1 和字段 2 为必填项。当用户选择类型 B 时,字段 3 是必填项——该字段应标记为红色,并且在无效时禁用保存按钮。还有更重的东西,比如空闲/占用的 DateTime-Ranges。

当我在 ViewModel 中做这些事情时,这绝对是糟糕的,因为大多数事情都是业务部分。

那么我如何才能做到这一点?

目前我有这个解决方法:

所有ValidationRules都在模型中作为简单的方法,例如

public string ValidateBirthday(string birthay)
{
    if (...)
    {
        return "Birthday should be…";
    }
    return string.Empty;
}
Run Code Online (Sandbox Code Playgroud)

在我的 ViewModel 中,我实现了 IDataErrorInfo,并像这样重定向到我的模型验证:

public string this[string columnName]
{
    get
    {
        switch (columnName)
        {
            case "Birthday":
                return Model.ValidateBirthday(Birthday);
            case "XXX":
                return Model.ValidateXXX(XXX);
            case "YYY":
                return Model.ValidateYYY(YYY);
            break;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我从来没有在一个例子中看到这样的东西(重定向到模型),所以我对我的实现非常怀疑。

我的解决方法是否可行,或者您是否发现任何问题?

我试图提供更多关于我的意思的信息......

我知道模型中的实现 INotifyPropertyChanged 和 IDataErrorInfo。

这适用于从视图到模型的直接绑定。

  1. 从视图到模型的直接绑定:

    公共类 PersonViewModel : INotifyPropertyChanged { 私有人 _personModel; 公共人人模型 { 得到 { 返回 _personModel; } set { if (_personModel != value) { _personModel = value; NotifyPropertyChanged(); } } }

    public PersonViewModel(Person person)
    {
        PersonModel = person;
    }
    …
    
    Run Code Online (Sandbox Code Playgroud)

    }

看法:

<DatePicker Text="{Binding PersonModel.Birthday}"/>
Run Code Online (Sandbox Code Playgroud)

最大的缺点是:WPF 控制所有强类型属性。

示例:用户在日期选择器中输入 07/20/2008,因此会通知 PersonModel 并且 PersonModel 可以检查这一点,当 OK 时,则 PersonModel 有效 => SaveButton 启用。

现在,用户在日期选择器中键入了“aaa”,WPF 将控制此验证,因为它绑定到强类型属性 (DateTime)。PersonModel 不会被告知,因此 PersonModel 仍然有效 => SaveButton 已启用!

所以对于那个“问题”,我需要ViewModel正确的。

  1. ViewModel 像这样包装模型:

    公共类 PersonViewModel : INotifyPropertyChanged { 私有人 _personModel;

    public string Birthday
    {
        get
        {
            if (_personModel. Birthday!= null)
            {
                return ((DateTime) _personModel. Birthday).ToShortDateString();
            }
            else
            {
                return String.Empty;
            }
        }
        set
        {
            if (_personModel. Birthday.ToString() != value)
            {
                DateTime dateValue;
                if (DateTime.TryParse(value, out dateValue))
                {
                    _personModel.Birthday = dateValue;
                    …
                }
                else
                {
                    …
                }
            }
        }    
    }
    
    public PersonViewModel(Person person)
    {
        _personModel = person;
    }
    …
    
    Run Code Online (Sandbox Code Playgroud)

    }

现在我不直接从 View 绑定模型。我从包装模型的 ViewModel 绑定属性。

<DatePicker Text="{Binding Birthday}"/>
Run Code Online (Sandbox Code Playgroud)

最大的优势是:现在我可以完全控制用户在字段中键入的内容。当用户在 Datepicker 中键入像 'aaa' 这样的字符串时,我可以捕捉到这个 => 将状态设置为无效并且 SaveButton 被禁用。

这就是为什么我不采用从 View 到 Model 的直接绑定的原因之一。其他原因是 readonly 属性。在模型中,我在每个属性上都有 get 和 set,但为了安全问题,我不会使用 get 和 set 提供模型中的所有属性。因此,这也可以通过 ViewModel 通过仅使用 get 包装此属性来解决。使用直接绑定你不能做所有这些事情。

我的观点是,我肯定会将我模型中的所有属性都包装在 ViewModel 中,但是如何IDataErrorInfo在模型中使用 nice (它仅适用于直接绑定)?

Joa*_*Fdz 3

您在这里混合了两个概念:业务对象和验证。

如今几乎每个系统都使用客户端-服务器架构,即使它是一个独立的应用程序。

在这种情况下,您有两个验证位置:

  • 客户负责在向服务器发送任何内容之前确保输入的数据有效,以增强用户体验并避免服务器过载和安全问题。

  • 服务器负责验证传入的数据,以避免格式错误、格式错误的数据和安全问题

还:

  • 业务对象(BO)是服务器使用的类,通常代表数据库。

  • 数据传输对象(DTO) 是服务器发送到客户端的类。

  • ViewModel既是 UI 的后端代码,也是 DTO 的包装器

您的模型对象不应该有任何逻辑,因为您将用一些在某些时候需要重用的代码破坏它们。

正如此处所公开的,您应该将该验证逻辑分离到仅了解该对象以及如何验证它们的服务中。这样,您就可以从 UI 使用验证服务。

您的Save按钮应该仅对 UI 更改做出反应,并且您只能从 ViewModel 获取这些更改。

基本上,您将在这里应用SOLID 原则:每一层都有非常明确的职责(模型 -> 数据、服务 -> 验证、dto -> 为客户端准备数据、视图模型 -> UI 交互)。所有代码都将易于使用、易于扩展和易于重构。

编辑

第一和第二个问题:UI 仅验证输入:数字字段中没有随机字符,文本字段中没有 sql 字符,日期格式正确等。

认为“如果这样那么那样”应该由后端处理,正如您所描述的:

  1. 单击“保存”。
  2. UI 数据有效。
  3. DTO发送到后端。
  4. 后端分析DTO无效。
  5. 后端返回发现的错误。
  6. UI 显示发现的错误。

第三个问题:我觉得这很正确。

第四个问题:DTO只是一个概念,您可以使用通过WCF通信的真正后端服务器,或者您可以只拥有一堆充当服务但在同一应用程序域中调用的类(就像任何其他项目参考一样) 。在任何一种情况下,您都可以选择发送和接收的数据。

你应该开始朝那个方向发展,然后看看什么更适合你。