14

在该领域几乎没有经验,我正在编写一个 WPF 智能客户端应用程序,使用 MVVM 与 WCF 后端通信,并且我真的很难从所有信息中做出正确的决定。这让我想到了一系列问题,我希望在这方面更有经验的人可以在这里解决这些问题。

例如,其中一个屏幕将允许输入订单并向订单添加订单行。

什么用作模型?

在 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 转换为INotifyPropertyChanged使用 Automapper 或类似方法实现的模型吗?还是应该直接在 ViewModel 中将 DTO 用作模型?

视图模型之间的通信

目前,我有一个带有 2 个选项卡(OrderOrderLines)的订单视图,其中包含 ViewModelsOrderViewModelOrderLineViewModel. 在订单选项卡上,我有一个ComboBox包含客户 ID 和名称的内容。当我在 上选择客户时OrderView,我需要告知OrderLineView客户已被选择,以便ComboBox仅显示属于该客户的产品。

问题

在这种情况下如何与OrderViewModel通信OrderLineViewModel

添加订单行并应用逻辑/业务规则

由于服务器级应用程序将被多个客户端(例如 PC、移动设备)使用。我想确保在服务器级应用程序中应用所有业务规则。例如,添加订单行时。如果是某种产品类型,则只有在客户具有某种认证的情况下才能添加。

然而,我所读到的关于 MVVM 的所有内容都表明该模型是适用于业务规则和行为的——所有这些示例都在客户端实现了该模型。理想情况下,我不想在客户端和服务器上重复相同的检查,所以我想知道如何确保不会发生这种情况。

问题

您是否允许用户添加无效行,将请求发送到服务器,让服务器应用相关规则并返回响应?或者在将请求发送到服务器之前,您是否以某种方式在智能客户端应用程序中应用逻辑?

我真的想在我在这里概述的所有领域都变得更好,我提前感谢您的任何回复。

谢谢

亚历克斯

编辑: 感谢大家的贡献,因为它帮助我在最佳前进方向方面变得更加清晰。所有的答案都很好,但我决定接受 Uri 的答案,因为它最符合我现阶段的想法。但是,我仍然不确定处理从 DTO 的 Id 到 ItemsSource 中的 SelectedItem 的转换的最佳方法,ItemsSource 是 ViewModel 的列表。我可以看到转换器可能会工作,但我会尝试寻找另一种解决方案。谢谢亚历克斯

4

4 回答 4

5

以下是我对您的问题的思考:

问题: 推荐的方法是将这些 DTO 转换为使用 Automapper 或类似方法实现 INotifyPropertyChanged 的​​模型吗?还是应该直接在viewmodel中将DTO用作模型?

答:我最喜欢的方法是遏制。我同意你的看法,DTO 不应该有任何东西,只有 getter 和 setter。尽可能保持干净,因此不应触发 INotifyPropertyChanged。我也不认为视图应该直接访问对象模型(如果没有其他原因,您没有更改属性的好处)。我的方法的缺点是 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(相对于 OrderLineDTO)的 ObservableCollection(相对于 List),所以包含不起作用。另请注意,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 通信?

答:如果需要沟通,确实应该用最简单的方式。两个视图模型类位于同一层。OrderVm 引用一个 OrderLineVm 列表,如果您需要从 OrderLineVm 类进行通信以进行订购,只需保留一个引用即可。

但是,我强烈认为不需要沟通。一旦视图被适当地绑定,我认为没有理由进行这种通信。绑定的 Mode 属性应该是“双向”,所以 UI 中的所有更改都会在 View Model 中更改。由于从 ObservableCollection 发送的通知,订单行列表的添加、删除将自动反映在视图上。

问题:您是否允许用户添加无效行向服务器发送请求让服务器应用相关规则并返回响应?或者在将请求发送到服务器之前,您是否以某种方式在智能客户端应用程序中应用逻辑?

答:除了服务器之外,在客户端进行数据验证没有任何问题。避免重复代码 - 拥有一个执行验证的程序集(可能是定义 DTO 的程序集),并在客户端中部署此程序集。这样,您的应用程序将响应更快,并且您将减少服务器上的工作量。

显然,您需要在服务器上进行数据验证(出于安全原因和种族冲突)。即使客户端上的验证通过,您也必须处理服务器返回错误的情况。

编辑:(跟进亚历克斯的评论):

显示下拉列表:我认为您感到困惑的根源在于实际上有两个独立的 ItemsSource(因此有两个单独的数据上下文):有一个订单行列表,每个订单行中嵌入的是 ProductID 列表,其中是填充组合框的项目。只有 SelectedItem 是 ProductLine 的属性。通常,可能的 ProductID 列表对于应用程序(或订单)应该是全局的。您将拥有整个表单的 ProductID 属性,并为其命名(例如 x:Key 或 x:Name)。然后,在 ComboBox 元素中仅引用此列表:

<ComboBox ItemsSource="{Binding Source={StaticResource ProductIDs}}"
          SelectedItem="{Binding Path=productId}"
          />
于 2012-01-24T13:04:29.610 回答
1

依次回答您的问题...

1)如果您不需要您的属性在更改时通知UI,那么就没有必要使用INotifyPropertyChanged-我认为您可以将Model直接绑定到View。如果它不添加任何额外的功能,则无需添加额外的层。但是,在大多数应用程序中,您将希望通过 UI 更改模型对象状态。在这种情况下,您将需要添加实现INotifyPropertyChanged. 您可以制作一个适应模型的视图模型,即将属性委托给底层模型,或者将模型对象状态复制到等效的视图模型。

为了避免编写大量非常相似的代码,即表示为模型对象和视图模型对象的相同域对象,我尝试尽可能使用代码生成。我喜欢使用 XML 来描述我的模型,以及使用 T4 模板来描述代码生成。

2)应该如何OrderViewModel沟通OrderLineViewModel?直接地!这些概念听起来非常紧密相关,我猜一个订单有多个订单行?在这种情况下,只需让每个视图模型引用另一个。如果两者在您的域中紧密耦合,则无需花哨的调解员。

3)好问题!我同意服务器应该应用验证。是否在客户端中复制某些此类验证取决于您的要求。如果您与服务器的通信快速且频繁,则您可以通过在用户编辑订单时与服务器通信并在订单从一个字段到另一个字段进行验证时提供良好的用户体验。然而,在许多情况下这是不切实际的。在客户端应用程序中应用简单验证是很常见的,但允许服务器进行更复杂的检查,例如检查唯一性等......

希望有帮助。

于 2012-01-22T22:20:41.537 回答
0

我有 2 年构建“富客户端”(在 WPF 中)的经验。

然后,在我的 WPF 智能客户端中,我引用了 DTO,但显然它没有实现 INotifyPropertyChanged,因为它纯粹用于传输。

默认情况下,错误
的 WCF 会在每个 DTO 上自动实现 INPC。
最好将 DTO 用作简单视图的 ViewModel,而对于更复杂的视图,使用组合“模式”。

视图模型之间的通信

“最佳”实践(阅读:几乎每个人都在做)是使用弱事件模式,以保持松散耦合。最著名的一个是来自 PRISM 库的 IEventAggregator,但那里有几个实现。

添加订单行并应用逻辑/业务规则

把客户端想象成一个网页:不要相信它。它是 .NET 代码,我们都知道入侵是多么容易。
这就是为什么,您应该在 WCF 服务上实施安全检查。

高温下,

呸。

于 2012-01-24T13:38:05.327 回答
0

我相信真正的问题是您希望对 MVVM 模式有多真实?

MVVM 以及类似的模式(如 MVC 和 MVP)背后的理念是关注点分离。虽然我也为这个话题辛勤工作,但我仔细研究了该模式试图完成的工作,并且选择变得更容易了。

使用 MVVM,您需要考虑三个问题:视图 (V)、模型 (M) 和视图模型 (VM)。看起来很明显,对吧?但是问问自己,每个人真正关心的是什么,如果我们开始混合关注点会发生什么——就像我们在其他地方混合关注点时会发生的那样。我们的代码变得更难更改。

考虑到这一点,考虑通过公开使用 UI 类型的属性让 UI 潜入 ViewModel 的情况。这在处理对话(MVVM 中令人头疼的主要来源)时很常见。假设您正在使用第 3 方控件集开发应用程序,并且 UI 类型是他们的其中之一。现在,如果您交换控件集而不是仅仅更改 UI 标记(或让设计人员这样做),则必须进行多项更改。

(这让我印象深刻,因为我刚刚进行了这样的努力,真正的 MVVM 应用程序可以快速重新设计,而其他应用程序的转换时间是 10-25 倍!)

同样的场景也会影响模式的“后端”。

模型的目的是将数据传输到/从您在应用程序中使用的任何持久性机制。这可能是 Web 服务、数据库、文本文件等。仅仅因为 WCF 添加了 INotifyPropertyChanged 等功能并不意味着建议使用它们。请记住,Microsoft 从事开发工具的业务。为了销售这些工具,他们需要在各种情况和级别下工作。例如,RIA 服务非常适合快速而肮脏的应用程序,但在应用于实际解决方案时会很快崩溃(至少根据我的经验)。

那么,如果您确实使用了包含模型,并且将所有属性委托给在 ViewModel 中保持状态的 Model 对象,并且 Model 的性质发生了变化,会发生什么?或者模型并不能满足您的所有需求。事实上,ViewModel 应该是一个适配器,它为 UI 提供了它需要操作的东西。与模型很少有 1:1 的关系,但我知道它会发生。

如果您在 6 个月后决定使用 REST 服务而不是 WCF,会发生什么情况?现在您的模型中没有 INPC 支持,因为您没有处理自动生成的代理类。虽然不像 UI 更改那样有形,但相同的想法适用于此处,这就是模式将模型分开的原因。

我的建议是您的第一直觉是使用 AutoMapper 将模型对象中包含的数据映射到您的 ViewModel 中,反之亦然。AutoMapper 可以非常轻松地处理您可能面临的阻抗不匹配问题,并在合同的一方或另一方发生变化时为您提供一个单一的地方进行更改。

2

您拥有的是一个对象模型,在这种情况下,具有事件、回调等是完全合法的。我会说您的 OrderViewModel 包含 OrderLineViewModel 对象的集合。子对象可以包含对父对象 (OrderViewModel) 的引用,并从那里拉取选定的客户。

3

首先,实现业务规则和验证的是 ViewModel,而不是 Model。其次,此类规则旨在为用户提供交互式体验。无论您在 ViewModel 中设置了什么规则,您都应该始终在服务器上执行完整性检查,以确保允许用户执行请求的操作并且数据对于持久性有效。

至于往返业务规则的问题,我会说不。我尝试在客户端应用程序中执行尽可能多的业务规则。一、提升了用户体验,减少了客户端所需的网络流量。我遵循的一条经验法则是,我绝不允许用户保留无效对象。注意:不准确或不完整的数据不等同于无效。无效数据导致异常。

于 2012-01-25T19:58:46.950 回答