MVVM和服务对象

时间:2011-06-08 10:56:18

标签: wpf web-services architecture mvvm

我们目前正在设计一个使用WPF的系统,该系统将使用Web服务与其他系统进行通信。

我们尝试尽可能少的图层和映射(通过坚持最简单的工作来降低成本)。

我们将使用MVVM模式。

所以我们必须有一个视图模型。

问题是我们可以使用从服务返回的对象作为Model对象,还是应该将它们映射到客户端中定义的模型对象?

4 个答案:

答案 0 :(得分:3)

虽然您可以使用模型中服务返回的数据对象,但有几个原因可以避免这种情况:

  1. 您最终可以将数据绑定要求(INotifyPropertyChangedINotifyCollectionChangedIDataErrorInfo等)引入服务数据对象。
  2. 使服务和WPF应用程序彼此独立地变得更加困难,尤其是在多个应用程序可能使用该服务的情况下。
  3. 您应该考虑使用模型中的Repository Pattern将服务通信封装为一个或多个Web服务存储库。 Web服务存储库允许您集中服务的访问逻辑,并为单元测试提供替换点,并为您提供缓存先前服务操作结果的机会。

    存储库充当不同域中的数据和操作之间的桥梁。存储库向数据源发出适当的查询,然后通常使用Data Mapper模式在表示之间进行转换,从而将结果集映射到业务实体。

    您的视图模型将使用服务存储库来检索或保留信息,其中存储库处理对服务的调用以及数据的服务表示形式到最终将数据绑定到的模型特定类的映射。 / p>

    您可以更进一步,为服务存储库定义通用接口,允许您围绕应用程序可能正在执行的基于CRUD的操作实现特定于服务的存储库。

    示例通用服务存储库接口:

    /// <summary>
    /// Describes a service repository that separates the logic that retrieves, persists and maps data to the 
    /// domain model from the business logic that acts on the domain model.
    /// </summary>
    /// <typeparam name="TChannel">The type of channel produced by the channel factory used by the repository.</typeparam>
    /// <typeparam name="TMessage">The type of data contract to map to the domain entity of type <typeparamref name="T"/>.</typeparam>
    /// <typeparam name="T">The type of domain entity mediated by the repository.</typeparam>
    /// <typeparam name="TKey">The type of the key that uniquely identifies domain entities within the repository.</typeparam>
    public interface IServiceRepository<TChannel, TMessage, T, TKey> : IRepository<T, TKey> 
      where T : class
    {
      /// <summary>
      /// Occurs when the repository transitions from one state to another.
      /// </summary>
      event EventHandler<StateChangedEventArgs> StateChanged;
    
      /// <summary>
      /// Gets the configuration name used for the service endpoint.
      /// </summary>
      /// <value>
      /// The name of the endpoint in the application configuration file that is used 
      /// to create a channel to the service endpoint.
      /// </value>
      string EndpointConfigurationName
      {
          get;
      }
    
      /// <summary>
      /// Gets the current state of the service repository.
      /// </summary>
      /// <value>
      /// The current <see cref="CommunicationState"/> of the service repository.
      /// </value>
      CommunicationState State
      {
          get;
      }
    }
    

    示例通用存储库接口:

    /// <summary>
    /// Describes a repository that separates the logic that retrieves, persists and maps data to the domain model 
    /// from the business logic that acts on the domain model.
    /// </summary>
    /// <typeparam name="T">The type of domain entity mediated by the repository.</typeparam>
    /// <typeparam name="TKey">The type of the key that uniquely identifies domain entities within the repository.</typeparam>
    public interface IRepository<T, TKey> 
      where T : class
    {
      /// <summary>
      /// Occurs when a repository action has been completed.
      /// </summary>
      event EventHandler<RepositoryActionCompletedEventArgs<T>> Completed;
    
      /// <summary>
      /// Occurs when a repository action fails to execute.
      /// </summary>
      event EventHandler<RepositoryActionFailedEventArgs<T>> Failed;
    
      /// <summary>
      /// Gets a value indicating if the repository has been disposed of.
      /// </summary>
      /// <value>
      /// <see langword="true" /> if the repository has been disposed of; otherwise, <see langword="false" />.
      /// </value>
      bool IsDisposed
      {
          get;
      }
    
      /// <summary>
      /// Adds a new <paramref name="entity"/> to the data source layer.
      /// </summary>
      /// <param name="entity">The entity of type <typeparamref name="T"/> to insert into the data source layer.</param>
      /// <param name="callback">
      /// The optional <see langword="delegate"/> method that will be executed after the <paramref name="entity"/> is insert into the data source layer.
      /// </param>
      /// <returns>
      /// The <see cref="IRepository{T, Tkey}"/> object that this method was called on.
      /// </returns>
      /// <exception cref="ArgumentNullException">The <paramref name="entity"/> is a <see langword="null"/> reference (Nothing in Visual Basic).</exception>
      IRepository<T, TKey> Add(T entity, Action<T, Exception> callback = null);
    
      /// <summary>
      /// Retrieves all entities of type <typeparamref name="T"/> from the data source layer.
      /// </summary>
      /// <param name="callback">
      /// The optional <see langword="delegate"/> method that will be executed after all entities of type <typeparamref name="T"/> are retrieved from the data source layer.
      /// </param>
      /// <returns>
      /// The <see cref="IRepository{T, Tkey}"/> object that this method was called on.
      /// </returns>
      IRepository<T, TKey> Get(Action<IEnumerable<T>, Exception> callback = null);
    
      /// <summary>
      /// Retrieves an entity of type <typeparamref name="T"/> from the data source layer that 
      /// matches the specified <paramref name="key"/>.
      /// </summary>
      /// <param name="key">The unique identifier of the entity of type <typeparamref name="T"/> to retrieve from the data source layer.</param>
      /// <param name="callback">
      /// The optional <see langword="delegate"/> method that will be executed after an entity of type <typeparamref name="T"/> that matches the specified <paramref name="key"/> is retrieved from the data source layer.
      /// </param>
      /// <returns>
      /// The <see cref="IRepository{T, Tkey}"/> object that this method was called on.
      /// </returns>
      IRepository<T, TKey> Get(TKey key, Action<T, Exception> callback = null);
    
      /// <summary>
      /// Removes an existing <paramref name="entity"/> from the data source layer.
      /// </summary>
      /// <param name="entity">An entity of type <typeparamref name="T"/> to delete from the data source layer.</param>
      /// <param name="callback">
      /// The optional <see langword="delegate"/> method that will be executed after the <paramref name="entity"/> is removed from the data source layer.
      /// </param>
      /// <returns>
      /// The <see cref="IRepository{T, Tkey}"/> object that this method was called on.
      /// </returns>
      /// <exception cref="ArgumentNullException">The <paramref name="entity"/> is a <see langword="null"/> reference (Nothing in Visual Basic).</exception>
      IRepository<T, TKey> Remove(T entity, Action<T, Exception> callback = null);
    
      /// <summary>
      /// Updates an existing <paramref name="entity"/> within the data source layer.
      /// </summary>
      /// <param name="entity">The entity of type <typeparamref name="T"/> to update within the data source layer.</param>
      /// <param name="callback">
      /// The optional <see langword="delegate"/> method that will be executed after the <paramref name="entity"/> is updated within the data source layer.
      /// </param>
      /// <returns>
      /// The <see cref="IRepository{T, Tkey}"/> object that this method was called on.
      /// </returns>
      /// <exception cref="ArgumentNullException">The <paramref name="entity"/> is a <see langword="null"/> reference (Nothing in Visual Basic).</exception>
      IRepository<T, TKey> Update(T entity, Action<T, Exception> callback = null);
    }
    

答案 1 :(得分:1)

您不必创建自己的模型图层,但是:如果服务发生变化,您将必须在引用模型的所有图层中反映出这些变化。如果你现在创建一个自己的模型层,你会更安全,但它更多的工作。经常这样:更多的工作将最终为你节省更多的工作。如果您拥有该服务并且您确定永远不会更改它(哈哈),则不需要模型层。

当然,这取决于服务检索对象是否完全符合您的需求。如果你使用来自大型物体的1,2个属性,它可能都不是一个很好的选择......

答案 2 :(得分:1)

是的你可以使用WCF服务对象是你的模型层(我们是),虽然像@fantasticfix所说,如果你的WCF服务器对象发生了变化,你必须找到修复它们的引用。

此外,如果要将整个Model公开给视图并绑定到Model的属性,那么您需要确保WCF服务器对象实现INotifyPropertyChanged

答案 3 :(得分:1)

我将MVVM用于没有服务的WPF应用程序,并使用来自同一解决方案的内存中dll服务检索Models。在这种获取模型的服务完全控制的环境中,没有任何额外的层是有意义的。所以我们只有Model(由内存dll服务提供),ViewModel对象和Views。

但是,如果您使用的是Web服务,我认为该服务的发展将与其使用无关。如果您的服务合同明天发生变化,您将不希望您的应用程序中断或者需要很多维护成本。因此,最佳做法是使用WCF模型在UI端重新创建自己的模型,然后对其进行处理。因此,如果合约明天发生变化,那么更改很可能是围绕将WCF模型映射到UI模型的逻辑,而不是超出。这个额外的层是值得的。

如果您的服务在您的控制之下并且您的应用程序足够简单,您可以尝试将服务模型用作MVVM模型。

不要尝试将ViewModel和Model合并为一个(通过在模型中实现INotifyPropertyChange)。我尝试了这个,随着应用程序复杂性的增加,它很快变得一团糟。此外,这种方式您的模型将膨胀与所有Command实现。它们应该只包含业务逻辑,而不是承担太多的责任来处理UI逻辑。