提问者:小点点

MVVM:绑定到模型,同时使模型与服务器版本保持同步


我花了很多时间来尝试为以下挑战找到一个优雅的解决方案。我一直无法找到一个不仅仅是解决这个问题的解决方案。

我有一个简单的视图、视图模型和模型设置。为了便于解释,我将保持非常简单。

  • 模型具有一个名为字符串类型的标题的属性。
  • 模型视图的数据上下文。
  • 视图具有一个数据绑定到模型上的标题的文本块。
  • ViewModel 有一个名为 Save() 的方法,它将模型保存到服务器
  • 服务器可以推送对模型所做的更改

目前为止,一切都好。现在,我需要进行两项调整,以使模型与服务器保持同步。服务器的类型并不重要。只要知道我需要调用 Save() 才能将模型推送到服务器。

调整1:

    < Li > < code >模型。Title属性需要调用< code > RaisePropertyChanged(),以便将< code >服务器对< code >模型所做的更改转换为< code >视图。由于< code>Model是< code>View的DataContext,这样做很好

还不错。

调整2:

  • 下一步是调用Save()以将从View所做的更改保存到Server上的Model。这就是我陷入困境的地方。我可以处理ViewModel上的Model. Property tyChanged事件,当模型发生更改时,它会调用保存(),但这会使它与服务器所做的更改相呼应。

我正在寻找一个优雅而合乎逻辑的解决方案,如果合理的话,我愿意改变我的架构。


共3个答案

匿名用户

过去,我编写过一个应用程序,它支持从多个位置“实时”编辑数据对象:应用程序的许多实例可以同时编辑同一个对象,当有人将更改推送到服务器时,其他所有人都会收到通知,并且(在最简单的情况下)会立即看到这些更改。以下是它的设计概要。

>

  • 视图总是绑定到ViewModels。我知道这是很多样板文件,但除了最简单的场景之外,直接绑定到模型是不可接受的;这也不符合MVVM的精神。

    ViewModel全权负责推送更改。这显然包括将更改推送到服务器,但也可能包括将更改推送到应用程序的其他组件。

    为此,ViewModels可能希望克隆它们包装的模型,以便它们可以像提供给服务器一样为应用程序的其余部分提供事务语义学(即您可以选择何时向应用程序的其余部分推送更改,如果每个人都直接绑定到同一个模型实例,则无法这样做)。隔离这样的更改需要更多的工作,但它也开辟了强大的可能性(例如,撤消更改是微不足道的:只是不要推送它们)。

    ViewModels依赖于某种数据服务。数据服务是位于数据存储和使用者之间的应用程序组件,用于处理它们之间的所有通信。每当 ViewModel 克隆其模型时,它还订阅数据服务公开的相应“数据存储已更改”事件。

    这允许viewmodel被通知其他viewmodel已经推送到数据存储的对“他们的”模型的改变,并适当地作出反应。通过适当的抽象,数据存储也可以是任何东西(例如特定应用程序中的WCF服务)。

    >

  • 创建视图模型并分配模型的所有权。它会立即克隆模型,并将此克隆公开给视图。由于依赖于数据服务,它会告知 DS 它想要订阅此特定模型更新的通知。ViewModel 不知道标识其模型的是什么(“主键”),但它不需要,因为这是 DS 的责任。

    当用户完成编辑时,他们与调用VM上的命令的视图进行交互。然后,VM调用DS,将更改推送到它的克隆模型。

    DS持续进行更改,并另外引发一个事件,通知所有其他感兴趣的VM已对Model X进行了更改;模型的新版本作为事件参数的一部分提供。

    已经被分配了相同模型所有权的其他虚拟机现在知道外部更改已经到来。他们现在可以决定如何在手头有所有拼图的情况下更新视图(模型的“以前”版本,已克隆;“脏”版本,即克隆;和“当前”版本,作为事件参数的一部分推送)。

    • 模型的INotifyPropertyChanged仅由视图使用;如果ViewModel想知道模型是否“脏”,它总是可以将克隆与原始版本进行比较(如果它已经保存,如果可能的话,我建议您这样做)
    • ViewModel以原子方式将更改推送到服务器,这很好,因为它确保数据存储始终处于一致状态。这是一种设计选择,如果你想做不同的事情,另一种设计会更合适
    • 如果ViewModel将<code>this</code>作为参数传递给“push changes”调用,则服务器可以选择不引发负责此更改的ViewModel的“Model changed”事件。即使没有,如果ViewModel看到模型的“当前”版本与它自己的克隆相同,它也可以选择什么都不做
    • 有了足够的抽象,就可以像将更改推送到shell中的其他视图一样轻松地将更改推送给其他计算机上运行的其他进程

    希望这有帮助;如果需要,我可以提供更多的澄清。

  • 匿名用户

    我建议将控制器添加到 MVVM 组合 (MVCVM?) 中以简化更新模式。

    控制器监听更高层的变化,并在模型和视图模型之间传播变化。

    保持物品清洁的基本规则是:

    • ViewModels只是保存特定形状数据的哑容器。他们不知道数据来自哪里或显示在哪里。
    • 视图
    • 显示特定形状的数据(通过绑定到视图模型)。他们不知道数据来自哪里,只知道如何显示它。
    • 模型提供真实数据。他们不知道它在哪里消费。
    • 控制器实现逻辑。例如为 VM 中的 ICommands 提供代码、侦听数据更改等。它们从模型填充 VM。让他们侦听 VM 更改并更新模型是有意义的。

    如另一个答案所述,您的DataContext应该是VM(或其属性),而不是模型。指向一个DataModel会很难区分关注点(例如,对于测试驱动开发)。

    大多数其他解决方案将逻辑放在视图模型中,这是“不正确的”,但是我看到控制器的好处一直被忽视。该死的MVVM缩写!:)

    匿名用户

    绑定模型以直接查看仅在模型实现 INotifyPropertyChanged 接口时才有效。(例如,由实体框架生成的模型)

    你可以做到这一点。

    public interface IModel : INotifyPropertyChanged //just sample model
    {
        public string Title { get; set; }
    }
    
    public class ViewModel : NotificationObject //prism's ViewModel
    {
        private IModel model;
    
        //construct
        public ViewModel(IModel model)
        {
            this.model = model;
            this.model.PropertyChanged += new PropertyChangedEventHandler(model_PropertyChanged);
        }
    
        private void model_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Title")
            {
                //Do something if model has changed by external service.
                RaisePropertyChanged(e.PropertyName);
            }
        }
        //....more properties
    }
    

    如果模型实现了INotifyPropertyChanged(这取决于),在大多数情况下,您可以将其用作DataContext。但在 DDD 中,大多数 MVVM 模型将被视为实体对象,而不是真正的域模型。

    更有效的方法是使用ViewModel作为DTO

    //Option 1.ViewModel act as DTO / expose some Model's property and responsible for UI logic.
    public string Title
    {
        get 
        {
            // some getter logic
            return string.Format("{0}", this.model.Title); 
        }
        set
        {
            // if(Validate(value)) add some setter logic
            this.model.Title = value;
            RaisePropertyChanged(() => Title);
        }
    }
    
    //Option 2.expose the Model (have self validation and implement INotifyPropertyChanged).
    public IModel Model
    {
        get { return this.model; }
        set
        {
            this.model = value;
            RaisePropertyChanged(() => Model);
        }
    }
    

    上述两个 ViewModel 属性都可用于绑定,同时不会破坏 MVVM 模式(模式 != 规则),这确实取决于。

    还有一件事… ViewModel依赖于Model。如果模型可以被外部服务/环境更改。是“全局状态”使事情变得复杂。

    相关问题