| | |
Chapter VIII | | Chapter X |
The series
WCF by Example is a series of articles that describe how to design and develop a WPF client using WCF for communication and NHibernate for persistence purposes.
The series introduction describes
the scope of the articles and discusses the architect solution at a high level.
Chapter overview
In the previous chapter, we introduced the Relay Command, and we demonstrated how
the Views using bindings in the XAML could easily invoke service methods. We also found a problem where the ViewModel was not notifying the View of the updates that
took place in our Model when invoking the customer service methods. In this chapter, we will discuss a pattern that leverages the notification of Model changes to the View.
The source code for this chapter can be found at CodePlex change set 93477.
The latest code for the eDirectory solution can be found at CodePlex.
As a result of the ServiceAdapter
and CommandDispatch
re-factor that was done
in Chapter VII, the following changes were done:
The INotifyPropertyChanged interface
The INotifyPropertyChanged
interface can be used by XAML DataContext
instances to notify the XAML View that a property in the model has changed.
The interface is quite simple; it just exposes an event named "PropertyChanged
", and the View subscribes
to this event so it can be notified when the event is triggered. If we examine the event handler, we will see that it requires an object, in our case the ViewModel,
and the name of property that has changed, this is passed as a string.
We could implement this interface in each of our ViewModel classes, but a better approach is to provide a base class that includes this function and a common implementation
so it can be easily re-used.
The ViewModelBase
| The ViewModelBase abstract class implements INotifyPropertyChanged ,
which provides an easy mechanism for the ViewModel classes to indicate when the Model property has changed. The ViewModel is assigned as the View's DataContext
in the ViewModel constructor, as can be seen below in the code snippet in line 01.
There are people indicating that the ViewModel should not be aware of the View, this is probably correct if the MVVM pattern is strictly followed. The main driver
to isolate the ViewModel from the View is for testing purposes. You may want to see what others have already said regarding this topic:
Thread by mtaboy:
why in your ViewModels you have a reference from its view.
Thread by stl7 re-coupling Views and ViewModels.
In the MVVM Light Toolkit, one of the uses of the Messenger component is precisely to decouple the ViewModel from Views. It requires
a little bit more of infrastructure but it resolves most of the problems when coupling is a problem.
|
public CustomerViewModel()
{
CustomerServiceAdapter = new ServiceAdapter<ICustomerService>();
Refresh();
01 View = new CustomerView { DataContext = this };
View.ShowDialog();
}
When the Model changes, we need to invoke RaisePropertyChanged
in the base class. There are many implementation examples of this pattern on the web, the
most common implementation is to pass the property name in a string parameter. But if the property is renamed or removed as a result of some re-factor, we need to
remember to update the code raising the event; this approach might not be the most adequate, so in our solution, we provide an overloaded method that uses Lambda Expressions
and avoids the mentioned issues at compile time.
The ViewModelBase
implementation is as follows:
public class ViewModelBase :INotifyPropertyChanged
{
01 public event PropertyChangedEventHandler PropertyChanged = delegate { return; };
02 protected void RaisePropertyChanged(string propertyName)
{
VerifyPropertyExists(propertyName);
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
03 protected void RaisePropertyChanged<T>(Expression<Func<T>> expression)
{
var memberExpression = expression.Body as MemberExpression;
if (memberExpression == null)
throw new ArgumentException("expression must be a property expression");
RaisePropertyChanged(memberExpression.Member.Name);
}
[Conditional("DEBUG")]
04 private void VerifyPropertyExists(string propertyName)
{
PropertyInfo currentProperty = GetType().GetProperty(propertyName);
string message = string.Format("Property Name \"{0}\" does not exist in {1}",
propertyName, GetType());
Debug.Assert(currentProperty != null, message);
}
}
The implementation of INotifyPropertyChanged
is done in a single line (line 01). We use here a trick so we don't have to check if the event handler is
null
when notifying the event subscribers.
The method in line 02 is the common implementation of the RaisePropertyChanged
pattern; a helper method (line 04) is used to notify if the property exists.
The method in line 03 is an enhanced approach, the one mentioned before, that removes the need to pass property names in strings.
The implementation
Just a minor change in our CustomerViewModel
is required to get it working with the new abstract class; with two lines, we will have our application working.
public class CustomerViewModel
01 : ViewModelBase
{
...
private void Refresh()
{
var result = CustomerServiceAdapter.Execute(s => s.FindAll());
Model = new CustomerModel { NewCustomerOperation = new CustomerDto(),
CustomerList = result.Customers};
02 RaisePropertyChanged(() => Model);
}
}
The first change is that CustomerViewModel
now inherits from ViewModelBase
(line 01), easy. The other change is when we want to notify the View that the Model
has changed; this occurs when the FindAll
method is called in the Refresh
method. Line 02 indicates how this is achieved, a Lambda Expression
is used to indicate which property of the ViewModel has been updated, in our case, Model. This is the only thing the View requires to be refreshed.
If we now execute the client and we enter some customer details like we did in the previous chapter, this time when the Save button is pressed, we can see how the
client is refreshed and it displays the correct data in the grid. It is worth noting how the application looks after the New Customer controls; the Refresh
method assigns a new blank instance of CustomerDto
to the Model.NewCustomerOperation
property which automatically resets the entered values, isn't it nice?
Chapter summary
There is not more to discuss about the INotifyPropertyChanged
pattern; it is simple and slick, which is good news. At this point, we have our application
running. This is a major milestone in our series, it means we are in a position to demonstrate to our client our work; we are ready to engage them in an effective
and productive manner; we could arrange workshops with the key users to demonstrate the new functionality. Try to convince them to use the application; as we mentioned,
they can execute the application from a USB stick if required, it could not be any easier. And if you are lucky, they might start logging defects and enhancements
in your ticket system. You may get busy at this stage, but be sure, you will get no major surprises for when the project reaches the UAT stage.
The next chapter is critical in the design of our application, Dependency Injection
will be introduced in our solution. We need to remove the references to the server components in our client. Once we cover DI, we will be ready for covering the NHibernate
and the WCF implementations. But remember, we don't need those two to gather the business requirements in an effective manner.