Using a ViewModel to Provide Meaningful Validation Error Messages

November 14, 2008

In my MSDN Magazine article about WPF data binding, I briefly mentioned how relying on the binding system to create input validation error messages does not usually lead to a very good user experience.  For example, suppose you bind the Text property of a TextBox to a property of type Int32, and the user types “foo” into the TextBox.  When the binding system tries to convert that string to an int, a FormatException is thrown and subsequently eaten by the data binding pipeline.  The standard technique of showing this validation error, as per most of the WPF documentation and examples, is to set ValidatesOnExceptions to true on the Binding assigned to the TextBox’s Text property.  This approach results in an error message like “Input string was not in a correct format” to be displayed to the user.  Unfortunately, most users have absolutely no idea what that cryptic message means.

The solution to this problem, as with so many others, is to use the Model-View-ViewModel (MVVM) pattern.  Since my article was about data binding, I did not have the liberty to show a better solution to this problem.  During a great conversation with my buddy Karl Shifflett the other night, this topic resurfaced, and I decided that it is time to show a better way.

Let’s take a step back and reassess the problem.  We have a property of a type that can be represented correctly by some, but not all, strings.  WPF does not currently ship with an editor control that knows how to work with this data type correctly (such as having a NumericUpDown control).  When the user types an invalid string into the TextBox that displays a textual representation of the property value, the WPF binding system swallows the parsing exception and, optionally, allows us to display the exception’s error message in the UI.  Most users do not understand what these error messages mean. We need a way to provide meaningful error messages, but to do that it seems that we must somehow crack open that black box known as the WPF data binding pipeline.  Since we cannot do that in a clean and simple way, we cannot take that approach.

The key to this problem is where the input value parsing occurs.  If we let the data binding pipeline parse the input text, we have no way of providing custom error messages, so we must handle the parsing ourselves.  In order to do that, we must expose a property of type String, not Int32, on a ViewModel object, and then bind the TextBox.Text property to that.  If we do that, the binding system has no need to parse the user’s input text for us, because a String is a String, and therefore no type conversion is required.  Once we have taken on the responsibility of parsing the input text, we suddenly have unfettered control over the error messages displayed in the UI.  This allows us to have a user-friendly editing experience, such as the one seen below:


In the demo app, which is available to download at the end of this post, there is a Model class called Person.  It is defined as:

Person class

The Person class implements “business rules” (to use the term loosely) by checking that the Age value is not set to an inappropriate value.  Note, the Age property of the Person class is of type Int32.  In order to make use of the technique I’m proposing, we must create a ViewModel class that wraps a Person instance.  Due to a severe lack of imagination, I named that class PersonViewModel…

PersonViewModel diagram

Within PersonViewModel is another Age property, but this time it is of type String:

PersonViewModel Age property

The TextBox in the Window has its Text property bound to this Age property, not the Age property on the Person object seen previously.  The user can type whatever they want into that TextBox, but the underlying Person object won’t have its Age updated unless they actually type in a valid whole number.  The first layer of defense against invalid input is in PersonViewModel’s implemention of IDataErrorInfo, as seen below:

PersonViewModel implements IDataErrorInfo

The second layer of defense against invalid input is in the Person class, which was listed in its entirety earlier in this post.  That validation logic is only invoked if the input text was able to be parsed to an integer.  As the screenshot below demonstrates, the Person object’s validation logic is still enforced.

Invalid Age message coming from Person object

Download the demo project here.  Note: be sure to change the file extension from .DOC to .ZIP and then decompress it.

Binding to (Validation.Errors)[0] without Creating Debug Spew

October 8, 2008

Microsoft’s documentation that shows how to display validation errors for elements says to use a binding like this:

This works, but it causes reams of debug spew to fill the Output window.  Why?  Because is tries to access the first item in the Validation.Errors attached property collection, but if there are no errors, an exception is thrown.  Here’s the type of debug spew that fills the output window:

System.Windows.Data Error: 16 : Cannot get ‘Item[]’ value (type ‘ValidationError’) from ‘(Validation.Errors)’ (type ‘ReadOnlyObservableCollection`1′). BindingExpression:Path=(0).[0].ErrorContent; DataItem=’TextBox’ (Name=’symbolTxt’); target element is ‘TextBox’ (Name=’symbolTxt’); target property is ‘ToolTip’ (type ‘Object’) TargetInvocationException:’System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. —> System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
at System.ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument argument, ExceptionResource resource)
at System.ThrowHelper.ThrowArgumentOutOfRangeException()
at System.Collections.Generic.List`1.get_Item(Int32 index)
at System.Collections.ObjectModel.Collection`1.get_Item(Int32 index)
at System.Collections.ObjectModel.ReadOnlyCollection`1.get_Item(Int32 index)
— End of inner exception stack trace —
at System.RuntimeMethodHandle._InvokeMethodFast(Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)
at System.RuntimeMethodHandle.InvokeMethodFast(Object target, Object[] arguments, Signature sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Reflection.RuntimePropertyInfo.GetValue(Object obj, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)
at MS.Internal.Data.PropertyPathWorker.GetValue(Object item, Int32 level)
at MS.Internal.Data.PropertyPathWorker.RawValue(Int32 k)’

I use the Output window all the time for debugging purposes, so having it fill up with these error messages breaks my workflow.  I decided to find a better way to display error messages, such that the Output window is not clogged with garbage.  Here’s what I came up with:

When you run my demo app, it looks like this:

The trick is to bind a ContentPresenter’s Content to the CurrentItem of Validation.Errors for the target element.   Binding to the CurrentItem means that we’re  binding to the CurrentItem property of the default ICollectionView that wraps the ReadOnlyObservableCollection<ValidationError> returned by the attached Errors property.  When the current item is non-null, that means there is a validation error; when it is null, there are no validation errors.  We can rely on ICollectionView to safely access the validation error, or not return anything if there are no errors.  That is what prevents the debug spew from pouring out.

The DataTemplate declared in the StackPanel’s Resources knows how to render a ValidationError object.  If the ContentPresenter has a null value, the template is not used to render anything.   That template could also render in a tooltip, if you prefer to keep with Microsoft’s example of showing validation error messages in a tooltip.

Download the demo project here.  Be sure to change the file extension from .DOC to .ZIP and then decompress the file.