We are currently designing a system using WPF which will communicate with other systems using web services.
We are trying to have as few layers and mappings as possible (reduce costs by sticking to the simplest thing that will work).
We will use the MVVM pattern.
So we must have a view model.
Question is could we use the objects that are returned from the service as the Model objects, or should we map these to model objects开发者_StackOverflow中文版 defined in the client?
While you can use the data objects returned from your services in your model, there are a few reasons to avoid this:
- You can end up introducing data binding requirements (INotifyPropertyChanged, INotifyCollectionChanged, IDataErrorInfo, etc.) into your service data objects.
- It becomes more difficult to evolve the service and the WPF application independently of one another, especially in the case where the service might be used by multiple applications.
You should instead consider using the Repository Pattern within your model to encapsulate your service commuication as one or more web service repositories. Web service repositories allow you to centralize the access logic for a service and provides a substitution point for unit tests, as well giving you an opportunity to cache the results of previous service operations.
Repositories act as bridges between data and operations that are in different domains. A repository issues the appropriate queries to the data source, and then it maps the result sets to the business entities typically by using the Data Mapper pattern to translate between representations.
Your view models will use a service repository to retrieve or persist information, with the repository handling the call to the service and the mapping of the service representation of the data to the model specific classes you will end up data binding to.
You can go a step further and define generic interfaces for a service repository that can allow you to implement service specific repositories around the CRUD based opperations your application might be performing.
Example generic 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;
}
}
Example generic 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);
}
You do not have to create an own model layer, BUT: if the service get changed you will have to reflect that changes in all layers which reference the model. if you create now an own model layer you are safer, but its more work. as so often: more work will save you eventually a lot more work in the future. If you own the service and you are sure you will never change it (ha ha) you don't need a model layer.
and of course it depends if the service retrieved objects correspond exactly to your needs. if you use 1, 2 properties from huge objects its possibly neither a very good choice...
Yes you can use WCF service objects are your model layer (we are), although like @fantasticfix said, if your WCF Server object changes you have to hunt down references to fix them.
Also, if you are exposing the entire Model to the view and binding to the Model's properties, then you need to ensure that the WCF server objects implement INotifyPropertyChanged
.
I use MVVM for a WPF application where there are no services and the Models are retrieved using in-memory dll services from the same solution. In such an environment where the service fetching the Models are in our total control, it makes sense to not have any extra layer. So we have only the Model (which arefetched by the in memory dll services), the ViewModel objects and the Views.
But if you are using Web Services, I assume the service will evolve independent of its usage. You will not want your application to break or have a lot of maintenance cost if your service contracts change tomorrow. So the best practice is to recreate your own Model on the UI side using the WCF models and then work over it. So if the contract changes tomorrow the change will most probably be around the logic of mapping WCF Model to your UI Model and not beyond. This extra layer is worth of the cost.
If your services are in your control and your application is simple enough, you can try using the service models as the MVVM model.
Do not try to merge ViewModel and Model into one (by implementing INotifyPropertyChange in the model itself). I tried this and it soon becomes a mess as your application complexity grows. Also, this way your Model will bloat with all Command implementations. They should only contain business logic and not take too many responsibilities of taking care of UI logic too.
精彩评论