Skip Navigation LinksHome > View Post

Silverlight Validation and ViewModel

UPDATE Part II available here

UPDATE Part III available here

Silverlight 3 brings some improvements to Validation for users of the MVVM (Model-View-ViewModel, or just ViewModel) pattern. Specifically, most controls now have a Visual State for a control that has generated binding validation errors.

The default InvalidFocused state looks pretty cool:

InvalidFocused state in SL3

And, with Silverlight's Binding supporting ValidatesOnExceptions (publishes a validation error if a setter throws an exception) and NotifyOnValidationError (sends a BindingValidationError event up tree) it's easy to use this with the ViewModel pattern.

Filling in the details

Most examples out there demonstrate how to use these capabilities so I won't go into the gritty details. However, I want to try and take that knowledge and try to apply it in a slightly more reusable way. There are two key problems I'm trying to solve.

The first problem... dealing with _your_ exceptions

The first is that it becomes difficult for us to tell if our ViewModel is in a valid state or not. Take the following very simple ViewModel:

public class MyViewModel : INotifyPropertyChanged
{
    private int _age = 1;

    public int Age
    {
        get { return _age; }
        set
        {
            if (value <= 0)
            {
                throw new Exception("Age must be greater than 0");
            }
            _age = value;
            OnPropertyChanged("Age");
        }
    }
    
    public void Save()
    {
        // TODO
    }
    
    // NotifyPropertyChanged implementation snipped for brevity

And the Xaml with the binding:

<TextBox Text="{Binding Path=Age, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}" />

As you can see, this ViewModel has been designed to leverage the ValidatesOnExceptions feature of Silverlight's Binding, raising an Exception if somebody tries to set the Age property to 0 or less (the image above shows how this would look).

The problem comes when we try to 'save' the state of the ViewModel. How can we tell if it's valid or not? Following a Validation Failure we throw an exception and don't set the value of the _age property. Therefore the ViewModel itself is still actually valid.

I can think of a myriad of solutions to this ranging from leveraging some of the features in existing validation frameworks like P&P's Validation Application Block (which is a good idea in any case) to simply allowing the Age property to be set _before_ throwing the Exception. Now the object will be in an 'invalid' state and we can prevent a 'save' taking place.

We're half way there...

The second problem... dealing with binding failures

However, the solution above only works if the TwoWay binding succeeds. What if the user enters 'hello' into the Age field?

Binding failure

That's handy but the update never made it to the ViewModel so, as far as it knows, it's still in a valid state so we can't prevent the save taking place.

A solution?

Since the ViewModel is all about the state of the view, it seems fair that it should be aware of *all* binding and validation failures like this. After all, it might want to host that information so another part of the View can display all the current failures. A validation summary if you will - but in a ViewModel way.

Of course, being a no-code-behind zealot. I'd like to achieve this with as little code-behind as possible in the view. Step forward Attached Behaviors.

My idea is to have an attached property called ValidationScope.Errors that can be attached to any FrameworkElement. The property would be of type IList and would handle the BindingValidationError for the element to which it's attached (creating a ValidationScope, effectively).

Then all we need to do is bind this attached property to an ObservableCollection on our ViewModel.

Here's the Attached Behavior/Property:

public class ValidationScope
{
    public static IList<ValidationError> GetErrors(DependencyObject obj)
    {
        return (IList<ValidationError>)obj.GetValue(ErrorsProperty);
    }

    public static void SetErrors(DependencyObject obj, IList<ValidationError> value)
    {
        obj.SetValue(ErrorsProperty, value);
    }

    // Using a DependencyProperty as the backing store for Errors. This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ErrorsProperty =
        DependencyProperty.RegisterAttached("Errors", typeof(IList<ValidationError>), typeof(ValidationScope),
        new PropertyMetadata(null, ErrorsChanged));

    public static void ErrorsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
    {
        FrameworkElement element = (FrameworkElement)obj;
        element.BindingValidationError += delegate(object sender, ValidationErrorEventArgs e)
            {
                if (e.Action == ValidationErrorEventAction.Added)
                {
                    GetErrors(obj).Add(e.Error);
                }
                else
                {
                    GetErrors(obj).Remove(e.Error);
                }
            };
    }
}

And here it is being used by our Xaml:

<Grid x:Name="LayoutRoot" Background="White" local:ValidationScope.Errors="{Binding Errors}">
    <StackPanel>
        <TextBlock Text="Name:" />
        <TextBox Text="{Binding Path=Name, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}" />
        <TextBlock Text="Age:" />
        <TextBox Text="{Binding Path=Age, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}" />
        <Button Content="Save" Click="Button_Click" />
    </StackPanel>
</Grid>

Clearly, we need an 'Errors' property on our ViewModel:

private readonly ObservableCollection<ValidationError> _errors = new ObservableCollection<ValidationError>();

public ObservableCollection<ValidationError> Errors
{
    get { return _errors; }
}

public bool IsViewStateValid()
{
    return Errors.Count == 0;
}

Notice that we can easily add an IsViewStateValid method too that simply sniffs the Errors collection.

And our save method?

pbulic void Save()
{
    if (!this.IsViewStateValid())
    {
        _dialogService.DisplayValidationDialog(Errors);
    }
    else
    {
        // TODO - SaveData
    }
}

In my example, I used one of Silverlight's new Child Windows to display my error dialog - passing the Errors collection so it could be used as a ViewModel for the dialog:

Invalid Dialog

:)

There are other challenges ahead though.. what happens if an object's state can be rendered invalid by a combination of properties? For example, maiden name should be blank if sex == male? In that case it becomes difficult to use the practice of throwing exceptions in setters as you start a chicken/egg race. For these reasons, I'd look at patterns like the Validation Application Block and lean on their concepts when implementing a full LOB validation approach.

Having said all that - before doing any of this you should definitely check out Silverlight 3's DataForm control. You could do much worse than spend 25 minutes watching Mike Taulty's excellent screencast: DataForm control. In particular, pay attention to some of the Validation support including the CustomValidation attribute.

Finally, if you'd like the code from the example I worked up before, you can download it here. Usual disclaimers apply - this is just demoware.

 
Josh Post By Josh Twist
1:54 AM
26 Jun 2009

» Next Post: Building an Atom-based media PC for the house
« Previous Post: Exciting changes afoot in Ukadc.Diagnostics

Comments are closed for this post.

Posted by Jonah Simpson @ 26 Jun 2009 6:38 AM
Hey Josh,

Interesting article...but instead of going through all this rigmarole, why not just use a string Property for Age in your ViewModel and do the conversion when propagating that value to the Model? That way you can check for invalid data types (since the binding will have no problem moving strings across to the VM) as part of your ViewModel validation?

Posted by Josh @ 26 Jun 2009 8:19 AM
@Jonah,

Your suggested approach only works for the smallest of examples IMO. I flat out don't think wrapping your whole model for use in a ViewModel works. It's a nightmare and throws up more challenges than it solves. Instead, my ViewModels nearly always expose my 'local' Model (might be different to that on the server in a Service Oriented application).

Also, a small but valid point compared to the last one, but you're now back to writing all the raw input parsing. Why not let the Binding engine do that for you (string -> int, string -> double) etc.

This approach is now incredibly lightweight (once you've written the ValidationScope) because it's reusable.

What do you think? Have you got the fully wrapped model approach to work?

Posted by Jonah @ 02 Jul 2009 7:57 AM
That's a good point Josh. In our app, we can't implement our ViewModel Properties as Proxies for the Model (we have an Async service pushing Model changes both directions, and we want to be able to allow the User to accept the service changes or to keep their own...kinda like a simple VCS).

The combination of wrapping the Properties + IDataErrorInfo on both the ViewModel and the Model gives us the ability to validate and send feedback to the UI while still handling the Async service changes. It would also probably help in the caveat you mentioned to your article where valid state is determined by a combination of Property values. This approach is also more work.

I guess the bottom line is no different than any other programming approach: use whatever works for your given situation. ; >

Good article and keep up the good work on the blog, I quite enjoy reading it!

Posted by Manuel Felício @ 14 Jan 2010 8:50 AM
Hi, this is just what I was looking for. I didn't know such event existed in the FrameworkElement class.

In this thread I was looking for a solution to a similar problem I had and I posted your solution there so that others can see.

http://forums.silverlight.net/forums/p/151790/348682.aspx#348682

Thanks

Posted by josh @ 14 Jan 2010 11:55 AM
Hi Manuel,

Glad to be of some help. Thanks for commenting.

Regards :)

Josh

Posted by Aaron @ 18 Jan 2010 1:11 PM
This is a great post, thanks. The only issue I still have is the scenario when adding a new item. If your VM contains a reference to a new object, the save button will attempt to save a new object, bypassing the BindingValidationError handler. In reality, the new object is invalid and technically should have errors.

Have you figured out a solution to this scenario by chance?

It seems to me that the Silverlight team needs to add more client side support, such as an IsValid property to System.Windows.Ria.Entity which would execute on the client and validate/invalidate all properties on the Entity before passing it over the wire...

Posted by Sachin @ 16 Sep 2010 8:31 AM
Hi,
I downloaded your code and try to run that but giving me the error.

The attachable property 'ValidationScope' was not found in type 'ValidationScope'.

Please help me out on this I am struggling since a long.

my email-address: sachin@jain200@gmail.com

Posted by Josh @ 17 Sep 2010 1:29 PM
Hi Sachin,

I just downloaded the code and opened it again and it worked fine. Did you delete the old reference to System.Web.Silverlight in the web project?

Thanks

Josh

© 2005 - 2014 Josh Twist - All Rights Reserved.