Using a ViewModel to Provide Meaningful Validation Error Messages

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:

invalidage_notwholenumber

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.

About these ads

17 Responses to Using a ViewModel to Provide Meaningful Validation Error Messages

  1. Matt Eland says:

    Very good as always. Normally I would not comment, but Ford Prefect and -42 really caught my attention. Your coding efforts and contribution to the WPF community are to be commended, as are your literary references.

  2. Hadi says:

    Another problem when setting ValidatesOnExceptions to true is that the exception message will be in english, but the application might actually be running in another language.

    A question that popup in my mind is, what people use in their LOB application to do the validation? Decorating your Model AND ViewModel with IDataErrorInfo is easy to do and has benefits as you describe here, but doesn’t that clutter your code, and when the model becomes more complicated you have business rules all over the place. Just my thoughts though…

    Thanks

  3. Pálesz says:

    I’ve made the same but my ViewModelBase abstract class extends your CommandSink too, so if i build all of my classes on the top of this one, i’ve got all the funcionality i need.

  4. Josh Smith says:

    Matt,
    I’m glad you appreciated the reference. You sound like the kind of guy who really knows where his towel is. :D

    Hadi,
    I disagree that adding validation logic “clutters” your code. The Model has validation logic that it should provide, and the ViewModel has a different type of validation logic, that only it should deal with. That separates the concerns cleanly, in my opinion. Good call about the localization issue.

    Palesz,
    Glad to hear this works out for you!

  5. Great post for MVVM! WPF Rocks!

    Cheers,

    Karl

  6. peteohanlon says:

    Josh – you hoopy frood you – this is a great post, but there is another way. A while back, I knocked together some things for Phil Laureano on using “injected” validation. If you like, I’ll pull this info together for you and pass it on to you, so you can have a look at it. If not, no biggie.

  7. Josh Smith says:

    Pete,

    How could I say no? Yes! :)

    Josh

  8. Sarafian Alex says:

    Hadi,
    in the last few months I’ve been developing an application that supports modules and multi language. The application does not know how many modules and their respective languages are installed, but it can represent the validation errors in any UI, with the correct language using Validation Error Messages.

    My personal opinion is that I disagree with validation on the setter property. There should be the extra effort to create Vallidation Rules in a reusable manner, that produce the application unified validation error messages. My only concern with this approach is, what happens when you require to validate against data that the controller knows dynamically?

    One of the reasons that I do not validate on Set is that my bussiness objects are actually typed rows, from a typed dataset. You can actually validate and set and error, but it hasn’t come to that yet.

    I have been trying to find time to post the knowledge I have accumalated in these past few months, in my blog, but there is the problem of the extra time needed to make the code Company Independent.

  9. [...] Josh Smith recommends Using a ViewModel to Provide Meaningful Validation Error Messages [...]

  10. Quang Tran Minh says:

    Hi Josh,
    Very nice post about MVVM! I did exactly the same when implementing validation.

  11. RobertT says:

    I think one could escape this multi-level defence system by means of attached behavior plugging to a TextCompositionManager.PreviewTextInput event and filtering incoming content, but that would prevent binding pipeline from showing error messages.

    peteohanlon, can I also say yes? =)

  12. Josh Smith says:

    RobertT,

    That’s true, but it wouldn’t work so well in more complex cases, such as when the user is editing a DateTime value. Or if you have a custom type converter that can convert string input to an instance of you custom type.

    Also, I think it’s good to have a strong View, in terms of helping with input validation, but your ViewModel shouldn’t rely on that.

    Josh

  13. Hi Josh,

    An interesting article – however it feels like you are really fighting the framework if you have to do this sort of thing. However there is another way that I think is a but more elegant. The recently introduced BindingGroup allows you to specify the point in the validation pipeline that you would like your rules ot be evaluated.

    With this in place, you can have a simple rule associated with your BindingGroup which performs a simple BindingGroup.TryGetValue for each of your bound objects properties to see if they can be parsed. If not an error is presented to the user. You can then delegate the business logic related rules to your business objects using ValidatesOnDataErrors for each binding. (Or if you could evaluate IDataErrorInfo.Error at the group level if you have more complex, multi-property rules).

    Here is a little example:

    With the following rule invoked when TextBox focus is lost:

    public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
    {
    BindingGroup bindingGroup = (BindingGroup)value;
    Person person = (Person)bindingGroup.Items[0];

    // validate the age
    object objValue = null;
    if (!bindingGroup.TryGetValue(person, “Age”, out objValue))
    {
    return new ValidationResult(false, string.Format(“Age is not a whole number”));
    }

    return ValidationResult.ValidResult;
    }

    (I hope your blog comments support XML!)

    All the best,
    Colin E.

  14. Ok – so it doesn’t! Here is the XAML for the above comment:

    [StackPanel Orientation="Vertical" x:Name="RootElement"]
    [StackPanel.BindingGroup]
    [BindingGroup]
    [BindingGroup.ValidationRules]
    [local:PersonValidationRule ValidationStep="ConvertedProposedValue" /]
    [/BindingGroup.ValidationRules]
    [/BindingGroup]
    [/StackPanel.BindingGroup]
    [TextBox Text="{Binding Age, ValidatesOnDataErrors=true}" LostFocus="TextBox_LostFocus"/]
    [TextBox Text="{Binding Name, ValidatesOnDataErrors=true}" LostFocus="TextBox_LostFocus"/]
    [Label Content="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type StackPanel}}, Path=(Validation.Errors)[0].ErrorContent}”/]
    [/StackPanel]

  15. Josh Smith says:

    Thanks Colin. You raise an interesting point. I’ll have to think more about this, but my initial reaction is that using custom ValidationRules and IDataErrorInfo to provide input validation is too “diverse.” I like to keep things simple, but perhaps what you’re proposing is simpler than it seems at first glance. :)

    On a side note, I don’t feel like my approach is “fighting” the framework. What it’s doing is working around the lack of type-specific editor controls. When WPF finally ships with things like numeric editors (or if you’re using 3rd party controls) then this is a non-issue, for most situations.

    Thanks,
    Josh

  16. Hi Josh,

    I see your point about being a little diverse, I seem to recall you posted a few comments on Vincents’s BindingGroup article about just how complex WPF validation is becoming.

    For what its worth, I feel my suggested pattern, where you validate against business rules within the business objects, exposing this through IDataErrorInfo, and validate purely UI framework parsing logic within the BindingGroup rules, is quite neat.

    You could of course modify the above rule that tests the parsing of bound UI controls to be more generic, by obtaining the object properties via the TypeDescriptor. This way you could get a standard set of error messages for parsing failures.

    Regards,
    Colin E.

  17. micahlmartin says:

    Josh -

    Someone raised an interesting question regarding the M-V-VM pattern, and mentioned that they were follwoing your lead with Crack.Net. Can you take a look at the question and perhaps shed a little more light on how to handle the situation most appropriately? I responded with how I do it, but I’m not sure it’s correct either.

    Thanks,

    Micah

    http://stackoverflow.com/questions/315180

Follow

Get every new post delivered to your Inbox.

Join 274 other followers

%d bloggers like this: