我花了很多时间来尝试为以下挑战找到一个优雅的解决方案。我一直无法找到一个不仅仅是解决这个问题的解决方案。
我有一个简单的视图、视图模型和模型设置。为了便于解释,我将保持非常简单。
模型
具有一个名为字符串类型的标题
的属性。 模型
是视图
的数据上下文。 视图
具有一个数据
绑定到模型上的标题
的文本块。ViewModel
有一个名为 Save()
的方法,它将模型
保存到服务器
服务器
可以推送对模型
所做的更改目前为止,一切都好。现在,我需要进行两项调整,以使模型与服务器
保持同步。服务器的类型并不重要。只要知道我需要调用 Save()
才能将模型推送到服务器。
调整1:
还不错。
调整2:
Save()
以将从View
所做的更改保存到Server
上的Model
。这就是我陷入困境的地方。我可以处理ViewModel
上的Model. Property tyChanged
事件,当模型发生更改时,它会调用保存(),但这会使它与服务器所做的更改相呼应。我正在寻找一个优雅而合乎逻辑的解决方案,如果合理的话,我愿意改变我的架构。
过去,我编写过一个应用程序,它支持从多个位置“实时”编辑数据对象:应用程序的许多实例可以同时编辑同一个对象,当有人将更改推送到服务器时,其他所有人都会收到通知,并且(在最简单的情况下)会立即看到这些更改。以下是它的设计概要。
>
视图总是绑定到ViewModels。我知道这是很多样板文件,但除了最简单的场景之外,直接绑定到模型是不可接受的;这也不符合MVVM的精神。
ViewModel全权负责推送更改。这显然包括将更改推送到服务器,但也可能包括将更改推送到应用程序的其他组件。
为此,ViewModels可能希望克隆它们包装的模型,以便它们可以像提供给服务器一样为应用程序的其余部分提供事务语义学(即您可以选择何时向应用程序的其余部分推送更改,如果每个人都直接绑定到同一个模型实例,则无法这样做)。隔离这样的更改需要更多的工作,但它也开辟了强大的可能性(例如,撤消更改是微不足道的:只是不要推送它们)。
ViewModels依赖于某种数据服务。数据服务是位于数据存储和使用者之间的应用程序组件,用于处理它们之间的所有通信。每当 ViewModel 克隆其模型时,它还订阅数据服务公开的相应“数据存储已更改”事件。
这允许viewmodel被通知其他viewmodel已经推送到数据存储的对“他们的”模型的改变,并适当地作出反应。通过适当的抽象,数据存储也可以是任何东西(例如特定应用程序中的WCF服务)。
>
创建视图模型并分配模型的所有权。它会立即克隆模型,并将此克隆公开给视图。由于依赖于数据服务,它会告知 DS 它想要订阅此特定模型更新的通知。ViewModel 不知道标识其模型的是什么(“主键”),但它不需要,因为这是 DS 的责任。
当用户完成编辑时,他们与调用VM上的命令的视图进行交互。然后,VM调用DS,将更改推送到它的克隆模型。
DS持续进行更改,并另外引发一个事件,通知所有其他感兴趣的VM已对Model X进行了更改;模型的新版本作为事件参数的一部分提供。
已经被分配了相同模型所有权的其他虚拟机现在知道外部更改已经到来。他们现在可以决定如何在手头有所有拼图的情况下更新视图(模型的“以前”版本,已克隆;“脏”版本,即克隆;和“当前”版本,作为事件参数的一部分推送)。
INotifyPropertyChanged
仅由视图使用;如果ViewModel想知道模型是否“脏”,它总是可以将克隆与原始版本进行比较(如果它已经保存,如果可能的话,我建议您这样做)希望这有帮助;如果需要,我可以提供更多的澄清。
我建议将控制器添加到 MVVM 组合 (MVCVM?) 中以简化更新模式。
控制器监听更高层的变化,并在模型和视图模型之间传播变化。
保持物品清洁的基本规则是:
如另一个答案所述,您的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。如果模型可以被外部服务/环境更改。是“全局状态”使事情变得复杂。