MVVM and service objects

We are currently developing a system using WPF that will interoperate with other systems using web services.

We try as few layers and mappings as possible (we reduce costs by sticking to the simplest thing that will work).

We will use the MVVM pattern.

Therefore, we must have a presentation model.

Question: can we use objects that are returned from the service as model objects, or match them with model objects defined on the client?

+4
source share
4 answers

You do not need to create your own model layer, BUT: if the service is changed, you will have to reflect these changes in all layers that reference the model. if you create your own model layer, you will be safer, but the more it will work. how often it is: more work will save you ultimately much more work in the future. If you have a service and you are sure that you will never change it (ha ha), you do not need a model layer.

and, of course, depends on whether the objects called up by the service correspond exactly to your needs. if you use 1, 2 properties from huge objects, this may not be a good choice ...

+1
source

Although you can use data objects returned from your services in your model, there are several reasons to avoid this:

  • You can enter data binding requirements ( INotifyPropertyChanged , INotifyCollectionChanged , IDataErrorInfo , etc.) into your service data objects.
  • It is more difficult to develop a service and a WPF application independently of each other, especially when the service can be used by several applications.

Instead, you should use the repository template in your model to encapsulate your service connection as one or more web service repositories. Web service repositories allow you to centralize the access logic for a service and provide a breakpoint for unit tests, as well as provide you with the ability to cache the results of previous service operations.

Repositories act as bridges between data and operations that reside in different domains. The repository issues the appropriate queries to the data source, and then matches the result sets to business objects, usually using the Data Mapper template to translate between views.

Your view models will use the service repository to retrieve or store information, while the repository handles the service call and mapping the data service view to specific model classes, resulting in data binding.

You can take one more step and define common interfaces for the service repository that will allow you to implement specialized repositories related to CRUD-based operations that your application can perform.

An example of a common service repository interface:

/// <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; } } 

An example of a common repository interface:

 /// <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); } 
+3
source

Yes, you can use WCF service objects - this is your model level (we are), although, like @fantasticfix, if your WCF server object changes, you need to look for links to fix them.

In addition, if you exposed the entire model to the presentation and binding to the model properties, you need to make sure that the WCF server objects implement INotifyPropertyChanged .

+1
source

I am using MVVM for a WPF application where there are no services, and models are retrieved using in-memory dll services from the same solution. In an environment where the service receiving the Models is in our full control, it makes sense to not have any additional layer. Thus, we only have a model (which is retrieved by the dll services in memory), ViewModel objects and views.

But if you use web services, I believe that the service will develop regardless of its use. You don’t want your application to crash or have a lot of maintenance costs if your service contracts change tomorrow. Therefore, it is best practice to recreate your own model on the user interface side using WCF models, and then work on it. Therefore, if the contract changes tomorrow, the change will most likely be related to the logic for displaying the WCF model in your user interface model, and not outside it. This extra level is worth the cost.

If your services are under your control and your application is fairly simple, you can try using service models as MVVM models.

Do not try to combine ViewModel and Model into one (by implementing INotifyPropertyChange in the model itself). I tried this and it will soon become a mess as your application complexity grows. In addition, this way your model will be inflated with all team implementations. They should contain only business logic and not take on too many responsibilities for maintaining the user interface logic.

+1
source

All Articles