Note: I rewrote this article on 12/29/2008 after speaking with my great friend and MVVM Master Josh Smith. I changed the ViewBase class to call a method on the ViewModelBase class instead of my original method of communicating from the View to the ViewModel using an ICommand. This is a cleaner and simpler solution. Originally I was trying to have a very decoupled solution. In my zeal to be pure, I overlooked the obvious and simpler approach.
The purpose of this article is to explain the necessity for monitoring binding exceptions caught in the UI that he data model would not be aware of. This material while explained in the context of M-V-VM, still applies to all WPF business applications.
A TextBox in the View is bound to an integer property on the Model. That integer property has an associated IDataErrorInfo validation rule that requires that the property value be between 0 and 100.
If the user enters a value less than 0 or greater than 100, the Model will report an error that the binding pipeline will pickup and if the TextBox has a Validation.ErrorTemplate, will display some error message to the user.
Now the user enters 5b and tabs to the next field. Since 5b is not an integer, the binding pipeline will get an exception when attempting to set the property value on the Model since there is a type mismatch.
Now the TextBox.Text property on the View is 5b, the Model does not know that the View has an invalid value and if the Model’s current value is within the limits of any applied validation rules, the Model thinks it’s valid.
So you can see that under these circumstances there is a disconnect between the View and Model. It is this disconnect that I would like to address and provide a solution for.
Other Solutions I Considered
Purchase or author UI controls that do not allow invalid input. It is very easy to author a Numeric only TextBox in WPF. However, when you get to the Date TextBox, I think things get a little more complex and can end up with the same disconnect I explained above.
I’ve never liked masked input controls and users really don’t like them either. Besides, why can’t my Date TextBox work like the Outlook 2007 Date Input Control? Play around with Outlook’s, you’ll be surprised what you can enter and it just figures it out for you. For example, gives these a try: mon, fri, 3, 24. Also I want to bring back the ” + “, ” – ” and ” . ” keys for date entry.
Fancy control aside, it’s possible for a control like this to have invalid input that does not translate to a date and you end up with the same disconnect; View is invalid and the Model is valid.
As a person who writes lots of blog and Code Project articles, I can’t use a 3rd party control because developers would need to purchase the 3rd party control license in order to compile my projects on their systems. So that is a non-starter. I also want readers to be able to take my code and use it at their work if they so desired. Not taking a dependency on a 3rd party solution makes this much simpler for readers and me too.
Josh Smith has also come up with a very good solution that you can read in this blog post, Using a ViewModel to Provide Meaningful Validation Error Messages. I actually really like Josh’s solution as it provides a great solution including reformatting and data type conversion error messages. My only push back was having to abstract all the properties of the Model on the ViewModel. I have run into some scenarios were doing this has its own side effects that I didn’t want to have to code around.
This is also the beauty of M-V-VM. While the pattern has some specific layers and components, implementation details can differ, sometimes a good bit but yield the same solid results. I would encourage all WPF LOB developers to look at many different M-V-VM implementations and draw your own conclusions. I came to mine after spending 3 weeks memory profiling many M-V-VM applications with different implementations of the M-V-VM pattern. The patterns shared here, use the minimum amount of resources and do not have memory leak problems.
- Solution must be simple
- Solution must be reusable and easy to implement in many applications
- Solution must be rock solid and impossible for the user to get into an invalid or disconnected state as described above
- Solution must not require that controls on Views implement anything outside of the normal Data Binding properties
- Solution must provide data for an error listing in the View when the programmer needs it
- Solution must provide a simple technique for the ViewModel to know if the View and Model are valid for the purposes of View notification and Save Command enabling and disabling
The CustomerViewModel exposes the Customer Model as a property. You will discover that this entire solution is contained in two base classes, ViewModelBase and ViewBase. ViewModels and Views that derive from these classes won’t have to deal with this code at all. The solution just works. At Microsoft we say, “IJW.” (it just works)
During the CustomerViewModel lifetime, if the Customer objects needs to be reloaded, for example after a record is inserted or updated to the database, a re-read of the record in the database is required. After the record is reloaded, the CustomerViewModel can raise the PropertyChanged event for the Customer property and the View will automatically refresh itself. Implementing M-V-VM in this fashion makes for a clean approach that does not require the reloading of the View or ViewModel when the data changes, just the reloading of the Model in necessary.
The ViewModelBase class exposes three methods for working with data binding exceptions in the UI, AddUIValidationError, RemoveUIValidationError and ClearUIValidationErrors. The first two are public methods called by the below ViewBase.ExceptionValidationErrorHandler. The ClearUIValidationErrors is a protected method that the deriving ViewModel can call as required; for example when the Model exposed by the ViewModel is reloaded and the UIValidationErrors need to be cleared.
The UIValidationErrorCount property returns the number of UIValidationErrors. Use this property when checking for the presence of UIValidationErrors.
The UIValidationErrorUserMessages property is what the View data binds to when displaying a list of UIValidationErrors to the user. It returns one string with the property name and message, “has invalid entry” for each UIValidationError.
The UIValidationErrorFullMessages property is for demonstration purposes for this application only. Please remove this from any other application as it serves no purpose other than for this demo application.
When the user enters a value in the UI that causes an exception to be thrown by the data binding pipeline, the ExceptionValidationErrorHandler gets the exception. The ExceptionValidationErrorHandler actually gets called when the exception is initially throw and when the exception is cleared. Getting notification of exceptions being added and removed makes tracking these very simple. As stated above the ExceptionValidationErrorHandler calls either the ViewModelBase.AddUIValidationError or ViewModelBase.RemoveUIValidationError method when an exception is added or removed.
UIValidationError Sequence Diagram
Thank Josh Smith for the great suggestion to put this complex sequence of events in a Sequence Diagram.
In addition to the UIValidationErrorUserMessages property the UIValidationErrorCount property has property changed notification that the UI can data bind to.
While these seems a little complex, application View’s and ViewModels simply need to derive from these or similar classes to get the functionality for free.
The test bench application allows has two sections. The top Customer section is the form for data entry. The UI and ViewModel Errors section displays UI Validation Exceptions and validation rule violations from the Model.
The Full Name field is a required entry from 1 to 30 characters long.
The Satisfaction Level field has a validation rule on the model requiring values to be from 0 to 100.
The red ” * ” indicate either a validation rule violation or that an exception was thrown when assigning a value to the Model property.
The Save button will only be enabled when the View and Model are both valid and without exceptions.
In the above image you can see that the Satisfaction Level is invalid. It’s not between 0 and 100. This is an IDataErrorInfo validation error. This is an example of a normal data entry validation rule violation and technique for displaying the condition to the user.
The Retired field, Nullable(Of Date) has an exception. “a11-12” is not a valid date. The Model knows nothing about the invalid Retired TextBox, but since the ViewModel as been informed of the invalid TextBox.Text property, the ViewModel will not allow the user to save any data until the exceptions have all been corrected and the Model validation rules all pass.
I’m showing both the UIValidationErrorUserMessages and UIValidationErrorFullMessages properties. Notice how the UIValidationErrorUserMessages displays a user friendly version of the UIValidationError.
In future MVVM example applications, I’ll demonstrate using these classes in a real world application. For now, understanding that these conditions can exist is very important and that we need to handle them in our applications.
This video link requires Microsoft Silverlight 1.0 or 2.0. If you do not have it, you will be prompted to install it when you click one of the links. After the short installation is completed, close the browser window that you did the install in and re-click the video you want to watch. You can also download it here. Windows XP or Vista required.
After downloading the below file, you must rename the file from .DOC to .ZIP. This is a requirement of WordPress.com
Updated 12/28/2008 Download Source (26KB)
Hope you have a great day!
Just a grain of sand on the worlds beaches.