Skip Navigation LinksHome > View Post

Silverlight, MVVM and Validation Part III

I've been working with Silverlight 3 in a number of LoB (Line of Business) scenarios for a while now and I'm consistently running in to a few dead ends with respect to Validation. Given I've posted twice before on Silverlight, MVVM and Validation:

I thought it only right to point out some of the shortcomings of these approaches and Silverlight 3 itself. The problem arises from the exciting, great-looking, new Validation states supported on some of the common input controls:

SL3 Validation

However, the only way to get a control into an 'invalid state' is to throw an exception in a bound setter. This is the key problem and leads to a number of inconsistencies as to how one should deal with validation, e.g.:
  1. If an exception is thrown within a setter then the property’s value should not be updated. This means that the bound object is still actually in a valid state and the controller (e.g. code-behind, presenter or viewmodel) has no visibility that the UI and bound model are now inconsistent, or that the UI is in an invalid state (because the model is still valid).
  2. When a TypeConverter fails to convert input for a binding, a similar problem to one described above arises – the model never receives the input and therefore the validation issue is unknown to the model (I tried to work-around these problems with limited success in this post: http://www.thejoyofcode.com/Silverlight_Validation_and_MVVM_Part_II.aspx - however, this falls flat in the face of cross-property validation).
  3. Throwing exceptions in property setters means we can’t bind to many generated models (e.g. WCF proxy types) as there is no way we can insert this logic into the generated type (no partial methods etc).
  4. Custom types that throw exceptions in setters can make cross-field validation very difficult (nay impossible, practically speaking). Furthermore, this approach may cause further difficulty for the framework in deserializing such a type depending on the order in which properties are set (e.g. Salutation and Gender could cause a failure during deserialization if the two aren’t in sync as the properties are set). Trying to work around these issues creates horrific spaghetti validation code that is best avoided altogether.
In consideration of the above, I've occasionally decided to abandon the cool new validation states in SL3 altogether in favour of something custom. I tried to invoke these states manually but was blocked:
  • You can manually trigger the change of visual state [e.g: VisualStateManager.GoToState(MyTextBox, “InvalidFocussed”, true); ]. The internal operation and flip between focussed and infocussed state appears to hang off a private _isInvalid member of the Control class – blocking the developer from working with the validation framework.
  • Even if we could overcome these issues, this behaviour invokes a template that binds directly to the Validation.Errors attached property for the tooltip. However, Validation.Errors is a read-only collection and the AddValidationError method is internal. Furthermore, the actual type ValidationError has an internal constructor. So all routes are blocked as far as I can see.
Whilst I love Silverlight the validation story in Silverlight 3 is somewhat incomplete with respect to the new Validation States that ship with the controls. However, forewarned is forearmed so think carefully before relying on the Silverlight 3 validation features if your validation requirements are anything more then very, very simple.

The good news

... is, that there is better support for Validation in Silverlight 4 (available in Beta at the time of writing). This is thanks to added support for the IDataErrorInfo interface which I have written about before with respect to its being supported inside WPF.

Based on the example in the previous post in this 'series': Silverlight, Validation and MVVM - Part II, here's an example of how you might implement this interface. I've chosen to do it this way because I'm a big ViewModel fan and already have a base class to which adding some validation logic seems to make sense. Additionally, I want to leverage the ValidationAttributes available in RIA services (even if I'm not using RIA services themselves). Ideally, the attributes could just be added to the 'model' properties without anymore work. And that's what I've shot for with my new base class.

Firstly, to manage this and return the results in the appropriate format I've created a ValidationManager class:

public class ValidationManager
{
    private readonly INotifyPropertyChanged _instance;
    private bool _isDirty = true;
    private readonly ValidationResultCollection _results = new ValidationResultCollection();

    public ValidationManager(INotifyPropertyChanged instance)
    {
        if (instance == null)
            throw new ArgumentNullException("instance");
        _instance = instance;
        _instance.PropertyChanged += delegate
            {
                _isDirty = true;
            };
    }

    public ValidationResultCollection Results
    {
        get
        {
            if (_isDirty)
            {
                Validate();
                _isDirty = false;
            }
            return _results;
        }
    }

    public ValidationResultCollection ResultsForMemberName(string memberName)
    {
        var results = Results.Where(r => r.MemberNames.Contains(memberName));
        return new ValidationResultCollection(results);
    }

    private void Validate()
    {
        _results.Clear();
        Validator.TryValidateObject(_instance, new ValidationContext(_instance, null, null), _results, true);
    }
}

public class ValidationResultCollection : List<ValidationResult>
{
    public ValidationResultCollection()
    : base()
    { }

    public ValidationResultCollection(IEnumerable<ValidationResult> results)
    : base(results)
    { }

    public override string ToString()
    {
        if (this.Count == 0)
        {
            return null;
        }
        
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < this.Count; i++)
        {
            sb.Append(this[i]);
            if (i < this.Count - 1)
            {
                sb.AppendLine();
            }
        }
        return sb.ToString();
    }
}

This class simply helps me to implement IDataErrorInfo by storing validation results and ensuring they're only re-generated if the object is 'dirtied' (we observe the INotifyPropertyChanged instance so we can know this). The validation results are then created on demand for the whole object (to ensure any class-level validators are invoked).

Next, I created my base class (based on my BaseViewModel from my snippets).

public class ValidatingViewModelBase : INotifyPropertyChanged, IDataErrorInfo
{
    private readonly ValidationManager _validationManager;

    public ValidatingViewModelBase()
    {
        _validationManager = new ValidationManager(this);
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler pceh = PropertyChanged;
        if (pceh != null)
        {
            pceh(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    protected virtual bool SetValue<T>(ref T target, T value, params string[] changedProperties)
    {
        if (Object.Equals(target, value))
        {
            return false;
        }

        target = value;

        foreach (string property in changedProperties)
        {
            OnPropertyChanged(property);
        }

        return true;
    }

    public string Error
    {
        get { return _validationManager.Results.ToString(); }
    }

    public string this[string columnName]
    {
        get
        {
            return _validationManager.ResultsForMemberName(columnName).ToString();
        }
    }
}

Now I just have to implement my model by inheriting from this class. And the best bit is I can just use attributes to apply validation logic to my type. Nice:

public class Person : ValidatingViewModelBase
{
    private string _name;

    [Required(ErrorMessage = "Name is required")]
    public string Name
    {
        get { return _name; }
        set
        {
            SetValue(ref _name, value, "Name");
        }
    }

    private string _salutation;

    [Required(ErrorMessage = "Salutation is required")]
    public string Salutation
    {
        get { return _salutation; }
        set
        {
            SetValue(ref _salutation, value, "Salutation");
        }
    }

    private int _age;

    [Required(ErrorMessage = "Age is required")]
    [Range(18, int.MaxValue, ErrorMessage = "Must be over 18")]
    public int Age
    {
        get { return _age; }
        set
        {
            SetValue(ref _age, value, "Age");
        }
    }
}

And here's a demo for you to try:

And, even to my own surprise, this works remarkably well. You can even add new objects and the validation invokes nicely!

but...

There are however, a couple of problems.

If the binding engine can't convert your input to the ViewModel type (for example, try inputting some nonsense string into the Age field above); then the view will display a notification (thank to ValidatesOnExceptions=true) but the ViewModel will be unaware of this failing and you won't be able to prevent the update going ahead. For this reason, I'd strongly recommend a hybrid approach utilising the ValidationScope from the previous post to capture Binding Errors and report them to the ViewModel. Note, I haven't included this in the sample above and, instead, have left that as an exercise for the reader.

The other problem to be aware of is silent conversion success. That is, if you enter 21.322 into the Age field then the binding will succeed. But, because that field is bound to an Int32, some of your data will be lost. As far as I know, there is now way to know this is happening so you'd have to implement something at the view to deal with this in a more sophisticated way (a new, derived TextBox control maybe?).

I've always felt IDataErrorInfo to be an 'immature' interface - it feels as though it's from a bygone era. Somebody in Redmond obviously feels the same and Silverlight 4 introduces a new INotifyDataErrorProperty which feels more elegant. The code above should modify to support this new interface with relative ease. Have a read of Mike Taulty's excellent posts on both interface for more information:

The source code for the example is available below. Please note that this is demoware - I'm posting ideas, not fully-furnished-frameworks-cum-mega-solutions. As always, your own mileage may vary.

 
Josh Post By Josh Twist
11:47 AM
15 Jan 2010

» Next Post: DotNetDevNet: Xamlathon Live '10
« Previous Post: The Binding you wanted from day one in WPF

Comments are closed for this post.

Posted by Andrew Hilton @ 20 Jan 2010 7:08 PM
Josh, have you considered encapsulating properties in a separate metadata class? In other words a field class with all appropriate properties (name, value, type, required, range etc). I know this is a departure from standard WPF field binding (not to mention objects returned from typical DAL's), but a library of fields could provide many benefits. In addition to simpler validation you could imagine further uses such as dynamic field layout (observable collection of fields and selective control creation via DataTemplates). Note I have not tried this - just a thought.
Andrew

Posted by josh @ 21 Jan 2010 6:50 AM
Hi Andrew,

You may be on to something but it's easy to fall into the 'frameworkitis' trap, where you solve one problem but create three others - having spent a lot of time implementing the new approach without progressing on the actual requirements.

Let me know if you try it and have any success.

J

Posted by Benjamin @ 09 Apr 2010 12:16 PM
Hi,

Thanx for sharing this.

However, I have an exception thrown when running my project. I'm using Silverlight 4 RC.

The validation error messages show up ok in red, but when I type in a value for the FirstName field and try to move to another field, I get an exception in NotifyPropertyChanged, when calling the PropertyChanged delegate. The message text for the exception is:

ArgumentOutOfRangeException crossed a native/managed boundary
Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index

The Call Stack is no help. It just says it's in "External Code".

Have you encountered this or do you know what's wrong? Could this be a SL 4 RC bug?

Thanx

Posted by GEB @ 12 Apr 2010 6:11 PM
Josh, these are great posts on validation! In your Part II post, you addressed the age-old problem of how to validate values if the user had never visited the control (i.e., clicked Save before visiting all the fields on the screen). I'm assuming that IDataErrorInfo doesn't do anything to solve this problem, and that something similar to your ValidationScope solution is still required (why you mentioned the hybrid approach)?

Also, if an application is using WCF RIA Services for virtually all the data entities, the only real advantage of IDataErrorInfo is that the application can now trigger errors outside of raising an exception within the setter. This is certainly useful, but not necessarily required for most data elements. Is there some additional reason that I should consider taking advange of IDataErrorInfo (and ValidatesOnDataErrors=True in the XAML)?

Posted by josh @ 18 Apr 2010 8:57 AM
Hi Gary,

Thanks for the comment. I have to be honest and say that I've not had chance to fully stretch the legs of RIA Services yet so I can't answer your question fully right now. I intend to spend sometime with RIA soon and will post my thoughts on Validation, IDataErrorInfo (and its new big brother INotifyDataErrorInfo) soon.

Josh

Posted by Luc @ 13 Oct 2010 10:06 AM
Hey,

Great work with this article!

Grtz
Luc

Posted by Hardy Wang @ 24 Oct 2010 3:45 AM
Nice article.

Just one question, is there a way I can do a validation only when I click on some button instead of property value change and validate automatically.

Thanks

Posted by Venkateswarlu Eraga @ 25 Nov 2010 3:11 AM
Very nice article..thank you

Posted by Nice, but wow. @ 18 Jan 2011 1:28 PM
Seems nice and all, but that's an awful lot of code for something so trivial. Remember the good ol' days when we could just drag a few validation controls onto the form, set a couple properties and be done.

Posted by Bill @ 28 Jan 2011 9:46 PM
ValidationResult

Where is it defined?

© 2005 - 2014 Josh Twist - All Rights Reserved.