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

标签 wpf mvvm business-rules idataerrorinfo

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

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

  • 我的第一个问题是关于在 ViewModel 中包装或不包装我的模型。
  • 当我不将我的模型包装在 ViewModel 中并将模型直接暴露给 View 时 – 那么我不明白为什么我需要一个 ViewModel(似乎毫无意义)
  • ViewModel出于各种原因应该包装模型:
  • 我不喜欢直接绑定(bind)到 Model (DateTime, int, ...) 中的强类型属性,因为当我这样做时 => WPF 会控制我对这些类型的验证。这真的很糟糕,因为当用户在 Datepicker 中写入“aaaa”时,我的模型是有效的(我的模型从不知道这一点,因为 WPF 控制强类型属性)并且启用了保存按钮 - 这真的是错误的。
  • 我没有向 View 公开我的模型的所有属性,我的 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;
    }
    

    在我的 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;
            }
        }
    }
    

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

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

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

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

    这适用于从 View 到模型的直接绑定(bind)。
  • 从 View 到模型的直接绑定(bind):

    公共(public)类 PersonViewModel : INotifyPropertyChanged
    {
    私有(private)人_personModel;
    公众人物人物模型
    {
    得到{返回_personModel; }

    {
    如果(_personModel != 值)
    {
    _personModel = 值;
    NotifyPropertyChanged();
    }
    }
    }
    public PersonViewModel(Person person)
    {
        PersonModel = person;
    }
    …
    

    }

  • 看法:
    <DatePicker Text="{Binding PersonModel.Birthday}"/>
    

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

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

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

    所以对于那个“问题”,我需要 ViewModel正确。
  • ViewModel 像这样包装模型:

    公共(public)类 PersonViewModel : INotifyPropertyChanged
    {
    私有(private)人_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;
    }
    …
    

    }

  • 现在我不直接从 View 绑定(bind)模型。我从包装模型的 ViewModel 绑定(bind)属性。
    <DatePicker Text="{Binding Birthday}"/>
    

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

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

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

    最佳答案

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

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

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

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

  • 还:
  • 业务对象 (BO) 是服务器使用的类,通常代表数据库。
  • 数据传输对象 (DTO) 是服务器发送给客户端的类。
  • View 模型 既是 UI 的后端代码,也是 DTO 的包装器。

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

    正如此处所公开的,您应该将该验证逻辑分成只知道该对象以及如何验证它们的服务。这样,您就可以从 UI 使用验证服务。

    您的 Save按钮应仅对 UI 更改使用react,并且您只能从 ViewModel 中获取这些更改。

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

    编辑

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

    正如您所描述的那样,认为“如果这个那么那个”应该由后端处理:
  • 单击保存。
  • UI 数据有效。
  • DTO 发送到后端。
  • 后端分析 DTO,它是无效的。
  • 后端发回发现的错误。
  • UI 显示发现的错误。

  • 第三个问题 :
    这对我来说是正确的。

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

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

    关于WPF、MVVM、带有验证的业务对象不太匹配,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23490015/

    相关文章:

    c# - TypeConverter 可以用于构造函数参数吗

    c# - 多个 ComboBox.DisplayMemberPath 选项?

    silverlight - EventTrigger 在 MVVM 中的 ItemsControl 中不起作用

    c# - 如何在 WPF 中为每个用户控件设置单独的 View 模型

    c# - 列表框中的项目溢出

    c# - ObservableCollection (ViewModel) 中的 MVVM ObservableCollection

    c# - ListView 和枚举

    c# - 保持在 C# 和 JS 中实现的相同逻辑同步

    dynamic - 开发人员如何让业务用户定义应用逻辑?

    java - Java或Python中基于开源的规则引擎