我在该领域的经验非常少,我正在编写一个使用MVVM与WCF后端通信的WPF智能客户端应用程序,并且我正在努力从所有信息中做出正确的决策。这让我想到了一系列问题,我希望这些问题可以在这方面有更多经验的人解决。
作为示例,其中一个屏幕将允许输入订单并向订单添加订单行。
什么用作模型?
在WCF服务上,我有以下简化的DTO:
public OrderDTO
{
string orderDetails { get; set; }
List<OrderLineDTO> OrderLines { get; set; }
}
public OrderLineDTO
{
int customerId { get; set; }
int productId { get; set; }
double quantity { get; set; }
}
一个WCF服务,它有以下方法:
public OrderService Order
{
CreateOrderResponse CreateOrder(OrderDTO order)
}
在我的WPF智能客户端中,我有一个对DTO的引用,但很明显它没有实现INotifyPropertyChanged
,因为它纯粹是为了传输。
问题
建议的方法是将这些DTO转换为使用Automapper或类似方法实现INotifyPropertyChanged
的模型吗?或者DTO应该直接在ViewModel中用作模型?
在视图模型之间进行通信
目前,我有一个包含2个标签(Order
和OrderLines
)的订单视图,其中包含ViewModels OrderViewModel
和OrderLineViewModel
。在订单选项卡上,我有一个包含客户ID和名称的ComboBox
。当我在OrderView
上选择客户时,我需要告知OrderLineView
已选择客户,以便ComboBox
仅显示属于该客户的产品。
问题
在这种情况下,OrderViewModel
如何与OrderLineViewModel
进行通信?
添加订单行并应用逻辑/业务规则
由于服务器级应用程序将由多个客户端使用,例如PC,移动设备..我想确保在服务器级应用程序中应用所有业务规则。例如,添加订单行时。如果是某种产品类型,只有在客户有特定认证的情况下才能添加。
但是,我读到的关于MVVM的所有内容都表明该模型适用于业务规则和行为 - 所有这些示例都在客户端实现了该模型。理想情况下,我不想在客户端和服务器上复制相同的检查,所以我想知道如何确保不会发生这种情况。
问题
您是否允许用户添加 无效 行,将请求发送到服务器,让服务器应用相关规则并返回响应?或者,在将请求发送到服务器之前,您是否以某种方式在智能客户端应用程序中应用逻辑?
我真的希望在这里概述的所有领域都做得更好,我提前感谢您的任何回复。
由于
亚历
修改 感谢大家的贡献,因为它帮助我在最佳前进方面变得更加清晰。所有答案都很好,但我决定接受Uri的回答,因为它最符合我现阶段的想法。但是,我仍然不确定处理从DTO的Id到ItemsSource中的SelectedItem的转换的最佳方法,ItemsSource是ViewModel的列表。我可以看到转换器可能有效,但我将尝试找到另一种解决方案。谢谢Alex
答案 0 :(得分:4)
以下是我对你的问题的看法:
问题: 推荐的方法是将这些DTO转换为使用Automapper或类似方法实现INotifyPropertyChanged的模型吗?或者DTO应该直接在viewmodel中用作模型吗?
答案:我最喜欢的方法是遏制。我同意你的看法DTO不应该有任何东西,除了吸气剂和制定者。保持尽可能干净,因此不应触发INotifyPropertyChanged。我也不认为View应该直接访问对象模型(如果没有其他原因,你没有改变属性的好处)。我的方法的缺点是ViewModel中有一些额外的代码,但我认为值得。
public class VmBase : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void raise( string propName )
{
if( PropertyChanged ) {
PropertyChanged( this, new PropertyChangedEventArgs(propName) );
}
}
}
public class OrderLineVm : VmBase {
private OrderLineDTO orderLine;
public OrderLineVm( OrderLineDTO ol ) {
orderLine = ol;
}
public OrderLineVm( ) {
orderLine = new OrderLineDTO();
}
int customerId {
get { return orderLine.customerId; }
set { orderLine.customerId=value; raise("customerId"); }
}
int productId {
get { return orderLine.productId; }
set { orderLine.productId=value; raise("productId"); }
}
double quantity {
...
}
}
通过垃圾收集的奇迹,OrderLineDTO只会创建一次(当它来自服务器时)并且只要需要就可以生存。有两个公共构造函数:一个是DTO(通常是来自服务器的对象),另一个是在客户端上创建的。
对于OrderVm,这有点复杂,因为你想拥有一个 OrderLineVm的ObservableCollection(与List相对)(与OrderLineDTO对比),因此包含将不起作用。另请注意,orderLines只有一个getter(您可以添加和删除订单行,但不要更改整个列表。在构建期间分配一次)。
public class OrderVm : VmBase {
private string _orderDetails;
public string orderDetails {
get { return _orderDetails;
set { _orderDetails=value; raise("orderDetails"); }
}
private ObservableCollection<OrderLineVm> _orderLines;
public ObservableCollection<OrderLineVm> orderLines {
get { return _orderLines; }
}
}
问题: 在这种情况下,OrderViewModel如何与OrderLineViewModel进行通信?
答案:如果需要沟通,确实应该以最简单的方式进行。两个View Model类都在同一层。 OrderVm引用OrderLineVm的列表,如果您需要从OrderLineVm类到订单的通信,只需保留一个引用。
但是,我强烈认为不需要沟通。一旦View适当绑定,我认为没有理由进行此类通信。绑定的Mode属性应为“双向”,因此UI中更改的所有内容都将在View Model中更改。此外,由于ObservableCollection发送的通知,对订单行列表的删除将自动反映在视图上。
问题:您是否允许用户添加无效行将请求发送到服务器让服务器应用相关规则并返回响应?或者,在将请求发送到服务器之前,您是否以某种方式在智能客户端应用程序中应用逻辑?
答案:除了服务器之外,客户端上的数据验证没有任何错误。避免重复代码 - 具有执行验证的单个程序集(可能是定义DTO的程序集),并在客户端中部署此程序集。这样,您的应用程序将更具响应性,并且您将减少服务器上的工作负载。
显然,您需要在服务器上进行数据验证(出于安全原因和种族冲突)。即使客户端上的验证通过,您也必须处理服务器返回错误时的情况。
编辑:(跟进Alex的评论):
显示下拉列表:我认为您混淆的原因是实际上有两个独立的ItemsSource(因此有两个独立的数据上下文):有一个订单行列表,并且每个订单行中嵌入的列表是ProductID,即组合框中填充的项目。只有SelectedItem才是ProductLine的属性。通常,可能的ProductID列表应该是应用程序(或订单)的全局。您将使ProductID成为整个表单的属性,并为其指定名称(例如x:Key或x:Name)。然后,在ComboBox元素中引用此列表:
<ComboBox ItemsSource="{Binding Source={StaticResource ProductIDs}}"
SelectedItem="{Binding Path=productId}"
/>
答案 1 :(得分:1)
依次回答你的问题......
1)如果您不需要属性在更改时通知UI,则无需使用INotifyPropertyChanged
- 我认为您可以将模型直接绑定到View。如果没有添加任何额外功能,则无需添加额外的图层。但是,在大多数应用程序中,您将希望通过UI更改模型对象状态。在这种情况下,您需要添加实现INotifyPropertyChanged
的View Model对象。您可以创建适应模型的视图模型,即将属性委托给基础模型,或将模型对象状态复制到等效视图模型。
为了避免编写大量相当类似的代码,即表示为模型对象和视图模型对象的相同域对象,我尝试尽可能使用代码生成。我喜欢使用XML来描述我的模型,使用T4模板来代码。
2)OrderViewModel
应该如何与OrderLineViewModel
进行沟通?直!这些概念听起来非常紧密,我猜想订单有多个订单行?在这种情况下,只需让每个视图模型引用另一个。如果两者在您的域内紧密耦合,则无需花哨的调解员。
3)好问题!我同意服务器应该应用验证。是否在客户端复制某些验证取决于您的要求。如果您与服务器的通信速度很快且频繁,则可以通过与服务器通信来提供良好的用户体验,因为用户编辑订单并在从一个字段到另一个字段进行验证时提供验证。但是,在许多情况下,这是不切实际的。在客户端应用程序中应用简单验证是很常见的,但允许服务器执行更复杂的检查,例如检查唯一性等...
希望有所帮助。
答案 2 :(得分:1)
我认为真正的问题是你希望MVVM模式真实存在吗?
MVVM背后的想法,以及MVC和MVP等类似模式,是关注点的分离。虽然我也喜欢这个话题,但我仔细研究了模式试图完成的内容,并且选择变得更加容易。
使用MVVM,您有三个问题:View(V),Model(M)和ViewModel(VM)。看起来很明显,对吧?但要问问自己每个人真正关心的是什么,如果我们开始混淆问题会发生什么 - 就像我们在其他地方混合担忧时那样。我们的代码变得更难改变。
考虑到这一点,请考虑通过公开使用UI类型的属性让UI进入ViewModel的情况。这在处理对话框时很常见(这是MVVM中头痛的主要原因)。假设您正在使用第三方控件开发应用程序,UI类型是他们的一个。现在,如果您交换控件集而不是仅仅更改UI标记(或让设计人员执行此操作),您必须进行多项更改。
(这在我的脑海中是新鲜的,因为我只是进行了这样的努力,真正的MVVM应用程序很容易重新设计,而其他人需要10-25倍的转换时间!)
同样的情况也会影响模式的“后端”。
模型的目的是向您的应用程序使用的任何持久性机制传输数据。这可能是一个Web服务,数据库,文本文件等。仅仅因为WCF添加了诸如INotifyPropertyChanged之类的功能并不意味着建议使用它们。请记住,Microsoft从事开发工具的业务。为了销售这些工具,他们需要在各种情况和水平上工作。例如,RIA Services非常适合快速和肮脏的应用程序,但在应用于实际解决方案时很快就会崩溃(至少在我的经验中)。
那么如果您使用包含模型并将所有属性委托给ViewModel中保持状态的Model对象并且模型的性质发生变化,会发生什么?或者模型并不能满足您的所有需求。事实上,ViewModel应该是一个适配器,它为UI提供了运行所需的功能。应该很少与模型建立1:1的关系,但我知道它会发生。
如果您在6个月内决定使用REST服务而不是WCF,会发生什么?现在,您的模型中没有INPC支持,因为您没有处理自动生成的代理类。虽然没有UI变化那么明显,但同样的想法在这里适用,这就是模式将模型分开的原因。
我的建议是用你的第一直觉去使用AutoMapper将Model对象中包含的数据映射到ViewModel,反之亦然。 AutoMapper可以很容易地处理您可能遇到的阻抗不匹配问题,并且只要合同的一方或另一方发生变化,您就可以在一个地方进行更改。
你拥有的是一个对象模型,在这种情况下,有事件,回调等是完全合法的。我会说你的OrderViewModel包含OrderLineViewModel对象的集合。子对象可以包含对父对象的引用(OrderViewModel),并从那里拉出所选的客户。
首先,实现业务规则和验证的是ViewModel,而不是Model。其次,这些规则是为用户提供交互式体验。无论您将哪些规则放入ViewModel,都应始终在服务器上执行完整性检查,以确保允许用户执行请求的操作,并确保数据对持久性有效。
关于往返业务规则的问题,我会说不。我尝试在客户端应用程序中强制执行尽可能多的业务规则。一,它改善了用户的体验,并减少了客户端所需的网络流量。我遵循的一条经验法则是我从不允许用户持久存在无效对象。注意:不准确或不完整的数据与无效数据不同。无效数据会导致异常。
答案 3 :(得分:0)
我有两年建立“富客户”的经验(在WPF中)。
在我的WPF智能客户端中,我有一个对DTO的引用,但很清楚 它没有实现INotifyPropertyChanged,因为它纯粹是为了 运输。
错误
默认情况下,WCF将自动在每个DTO上实施INPC
最好将DTO用作ViewModel用于简单视图,对于更复杂的视图,使用组合“pattern”。
在视图模型之间进行通信
“最佳”练习(阅读:几乎每个人都这样做)是使用弱事件模式,以保持松散耦合。最着名的是来自PRISM库的IEventAggregator,但有几个实现。
添加订单行并应用逻辑/业务规则
将客户端视为与网页相同:不要相信它。它是.NET代码,我们都知道入侵是多么容易 这就是为什么你应该在你的WCF服务上实现安全检查。
HTH,
巴布。