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:
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); }