Friday, January 25, 2008

Model-View-ViewModel Pattern for WPF: Yet another approach

A few days back, I posted an example here of implementing Model-View-Controller in a WPF scenario. On a side note, WPF specialist Josh Smith posted his own rendition here, which is also a very nice example and a more humorous choice of program.

A quick redux:
I was originally bamboozled by the two-way databinding features of WPF. I architected a GUI that was directly bound to my business objects.This was a bad idea for me. Why? Because my business objects do not support any form of invalid state. As the user edits the screen, he/she is directly editing a business object.

The object is in a transitional state at this point. It should be OK to allow invalidity as the user edits, but my business object will not allow invalid values, so validation and user interactivity breaks down in this approach.

I need something that stands between the GUI and the BOs. I know! Lets pull out the classic Model-View-Controller Pattern! Now I can allow the user to edit the UI as they please. When the user clicks some sort of an "Apply" or "OK" button, an event is sent to the controller, where an attempt is made to perform some of the user's changes. At the very least, validation can occur at this point; the business object will throw any exceptions and they can be caught, logged, and displayed, put on the evening news, and troops will be deployed to Norway. If there are no exceptions, the business logic is successfully performed, and the view's bindings are updated to reflect the changes in the model. This is a popular design with the web-based guys, since their views have no state.

I wasn't happy with this. Why? I'm not working web pages right now; I want two-way databinding. I have to write plumbing code to send data from the GUI to the controller? I have to write plumbing code to send changes from the model to the GUI? Wasn't the whole point of WPF databinding to get rid of this plumbing code?

The Model-View-ViewModel Pattern: Binding to Facsimile Objects

I want to dynamically bind to something that doesn't really support dynamic binding. What to do? The MVVM approach is a very attractive compromise between two-way databinding and rigid model integrity!

Before I begin, I owe references to the masters and creators of this design:
  • Dan Crevier has the complete and unabridged version of MVVM. Go read straight from the source to get the full picture. The guys is very sharp, and savvy with WPF implementation details that I may never fully understand.

I'll break it down into the key elements:
  • Forget the controller, you wont be needing it here
  • You can keep the "IView" interface from the previous example, but they are much less useful, since we will not be mocking a view in the unit tests any more. I threw them away to keep things simple.
  • The Model objects don't change.
  • The ViewModel is a "made for UI" object that represents the Business Object.
  • The UI supports direct, two-way intimate binding with the ViewModel
  • The ViewModel supports invalid states. It can easily implement IDataErrorInfo here
  • When the user "commits" the edits, the ViewModel attempts to "update" the model object that it is representing.
Here is my "Pete-brand" abstract concept of a ViewModel:

/// <summary>
/// A view model is a facsimile of a business object that is safe for databinding and invalidation
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class ViewModelBase<T> : IDataErrorInfo where T : BusinessObject
{
/// <summary>
/// The model that the ViewModel is representing
/// </summary>
protected T Model
{
get;
set;
}
/// <summary>
/// ViewModels can represent new Models that havent been created yet.
/// If the model is null, then a "construction" will occur uppon the call to ApplyChanges
/// </summary>
protected bool IsNewModelInstance
{
get { return Model == null; }
}
/// <summary>
/// Setting up for a new model instance
/// </summary>
public ViewModelBase()
{
Model = null;
}
/// <summary>
/// setting up for editing an existing model
/// </summary>
/// <param name="model"></param>
public ViewModelBase(T model)
{
Model = model;
}
/// <summary>
/// Attempt to create or edit the underlying business object
/// </summary>
public void ApplyChanges()
{
try
{
if (IsNewModelInstance)
CreateModel();
else
ApplyToModel();
UpdateViewModelFromBusinessObject();
}
catch(Exception e)
{
UpdateViewModelFromBusinessObject();
throw e;
}
}
/// <summary>
/// edit the underlying model
/// </summary>
protected abstract void ApplyToModel();
/// <summary>
/// create the underlying model
/// </summary>
protected abstract void CreateModel();
/// <summary>
/// Any changes to the model should be reflected in the ViewModel
/// </summary>
protected abstract void UpdateViewModelFromBusinessObject();

#region IDataErrorInfo Members
///<summary>
///Gets the error message for the property with the given name.
///</summary>
///
///<returns>
///The error message for the property. The default is an empty string ("").
///</returns>
///
///<param name="columnName">The name of the property whose error message to get. </param>
public abstract string this[string columnName] { get; }
///<summary>
///Gets an error message indicating what is wrong with this object.
///</summary>
///
///<returns>
///An error message indicating what is wrong with this object. The default is an empty string ("").
///</returns>
///
public abstract string Error { get; }
#endregion
}

A few key pieces here:
  • A ViewModel can be used to construct a new business object, or it can be used to edit existing ones.
  • A ViewModel exposes its own copy of public properties of the inner object, and they can indeed be as invalid and cockeyed as the user pleases.
  • When the user clicks "Apply", the ApplyChanges method will be called, which will either attempt to construct a new BO or edit an existing BO using the properties from the ViewModel. Exceptions may be thrown by the BO, and they will be caught and displayed in the normal fashion.
The program itself is functionally the same thing. The BlogEntry editor window is now tightly bound to a BlogEntryViewModel. It is the responsibility of BlogEntryViewModel to attempt to plug property changes in to the BlogEntry.

Observations along the way:
  • As you allow the user to do more and more before clicking apply, the chances of exceptions and validation problems increases. All the better reason to implement IDataErrorInfo, I suppose.
  • I am still not happy with the IDataErrorInfo interface. The ViewModel has validation, the business objects have validation, I need to find a way to reuse the validation from the BOs and not rewrite this!
  • As you allow the user to do more and more before clicking apply, the Apply() method of the ViewModel object will become more complicated as it tries to synchronize the business objects with its own state. These are a lot of Moving Parts.
  • The ViewModel and the Model contain the bulk of the intelligence, and they are very easy to isolate and test.
  • Josh Smith and others tell me that the CSLA framework handles this tie between the GUI and the BO in a graceful manner. I want to find the best solution, I will be researching here next.
The source code is a derivative of the MVC work. Download the new source code here

3 Comments:

Blogger Josh Smith said...

Another stellar post! This is a great usage of MVVM. As for the concern about not duplicating validation logic across your Model and ViewModel, perhaps using the CSLA notion of validation rule objects would be useful here...

Thanks,
Josh Smith

1:33 PM  
Blogger Peter Weissbrod said...

Indeed I was in the middle of a CSLA video demo this very moment...
Pretty impressive to say the least!

2:05 PM  
OpenID dthomp30 said...

I think if the ViewModel were to provide rundimentary validation rules when the view is constructed, the problem of validation becomes less and less significant when the user selects the apply button. I'm suggest the Model / ViewModel should be allowed to do both, validate on user input as well as on apply

12:00 PM  

Post a Comment

<< Home