Silverlight, Validation and MVVM - Part II
The new Validation states for controls in Silverlight 3 sure look nice but there are a number of limitations. For starters, you can only invoke them through:
- An exception thrown by a bound property setter
- An exception thrown by a ValueConverter
The latter feels particularly unsavoury as you'll need to reuse the same converter wherever you want to bind to that property - a big violation of the DRY principle.
You can't even use the new ValidationAttributes from System.ComponentModel.DataAnnotations. Well, actually you can but you'd have to this inside the setter:
[Range(18, int.MaxValue, ErrorMessage="Must be 18 or over")]
public int Age
{
get { return _age; }
set
{
Validator.ValidateProperty(value, new ValidationContext(this, null, null)
{
MemberName = "Age",
});
_age = value;
OnPropertyChanged("Age");
}
}
(You can imagine an enhancement to this pattern using the SetValue concept I shared with my
new snippets the other day).
What's particularly tricky is to display the invalid state if the value is never changed by the user. For example, you have a name field that is required:
[Reguired(ErrorMessage="Name is required")]
public string Name
This is bound to a TextBox:
<TextBox Text="{Binding Name, Mode=TwoWay, ValidatesOnExceptions=True, NotifyOnValidationError=True}" x:Name="NameTextBox" />
<Button Content="Save" x:Name="SaveButton" />
The problem is, the user might click save without ever entering the a name. Oh no!
Sure, we can catch that in our code (using the Validator type again, for example). However, there's no easy way of forcing the control to display the error. The only way to achieve this is to force the binding to update programmatically, like so:
BindingExpression binding = NameTextBox.GetBindingExpression(TextBox.TextProperty);
binding.UpdateSource();
Now, to get this working on any scale is going to require code-behind. Lots of code-behind. And everybody knows I hate this. The whole validation story at the moment isn't going to play at all well with Model-View-ViewModel (MVVM).
What we need is an easy way to Update all bindings from our ViewModel...
And so, I set about finding a more declarative way of achieving solving. My approach builds on my previous post
Silverlight Validation and ViewModel.
Re-introducing the ValidationScope
In this update - the ValidationScope class becomes much more important and has more to offer than just an attached properties/behavior.
It now becomes an integral part of your ViewModel. Let's walk through a scenario. Here we'll have a ViewModel that exposes a Person property of type Person:
// properties snipped down for brevity
public class Person
{
[Required(ErrorMessage= "Name is required")]
public string Name {}
[Required(ErrorMessage = "Salutation is required")]
public string Salutation {}
[Range(0, int.MaxValue, ErrorMessage = "Must be over 18")]
public int Age {}
}
Nice and easy. Now the validation scope comes into play - we add an instance to our ViewModel because we'll access it via binding. To be honest, this could go almost anywhere you like provided it's accessible in a Binding (resources, in the Person class itself, anywhere you like!).
// properties snipped down for brevity
public class MainViewModel : INotifyPropertyChanged
{
public ObservableCollection<string> Salutations {}
public Person Person {}
public ValidationScope PersonValidationScope {}
}
So there's our model and our ViewModel. Now for some view - our Xaml (again, simplified for brevity):
<StackPanel local:ValidationScope.ValidationScope="{Binding PersonValidationScope}">
<TextBox
Text="{Binding Person.Name, Mode=TwoWay, ValidatesOnExceptions=True, NotifyOnValidationError=True}"
local:ValidationScope.ValidateBoundProperty="Text" />
<TextBox
Text="{Binding Person.Age, Mode=TwoWay, ValidatesOnExceptions=True, NotifyOnValidationError=True}"
local:ValidationScope.ValidateBoundProperty="Text" />
<ComboBox
ItemsSource="{Binding Salutations}"
SelectedItem="{Binding Person.Salutation, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}"
local:ValidationScope.ValidateBoundProperty="SelectedItem" />
<Button Content="Save" Click="SaveButtonClick" />
</StackPanel>
And the code-behind:
//Note - I'd normally use Prism's DelegateCommand and commanding support to avoid this code-behind but don't want to muddy the example
private void SaveButtonClick(object sender, RoutedEventArgs e)
{
_personViewModel.Save();
}
Finally, the Save method on the ViewModel
public void Save()
{
// This causes all registered bindings to be updated
PersonValidationScope.ValidateScope();
if (PersonValidationScope.IsValid())
{
// Save changes!
}
}
How it all works
Whilst getting here took me a whole morning of confusion - it's actually quite straightforward.
First, we use an attached behavior to pass the FrameworkElement we want to be the conceptual 'validation scope' within the VisualTree to our actual ValidationScope instance:
<StackPanel local:ValidationScope.ValidationScope="{Binding PersonValidationScope}">
Then we specify the property who is bound and might need a refresh
for each control:
<TextBox local:ValidationScope.ValidateBoundProperty="Text" />
<ComboBox local:ValidationScope.ValidateBoundProperty="SelectedItem" />
Sadly, this is really a violation of the DRY principle anyway but it does have the added advantage of having to opt in your Bindings to the ValidationScope.
Finally, when we're ready, we tell the ValidationScope to update all the bindings:
PersonValidationScope.ValidateScope();
This kicks the process into action with a crawl of the VisualTree inside the FrameworkElement registered as our scope (the StackPanel in this case) and hunts out any attached ValidateBoundProperty properties wired to controls. When it finds them it looks for the appropriate DependencyProperty (Text and SelectedItem in our demo)
demoware
If you're interested in this you'll want to see a working demo and the source code. Here's the former and the latter will follow in a minute. To bush the barrier, we actually have
two validation scopes in the same view, side-by-side
Before you get to the source - bear in mind this is demoware/spike standard only and probably needs work. The usual disclaimers apply. Here are just some thing that haven't even been thought about in the current implementation:
- binding to the same ValidationScope from two different FrameworkElements
- performance could be improved, quite a bit of reflection
- nested ValidationScopes
- many more I'm sure
Finally, before you rush off make sure to double check that the new
DataForm control and/or
.NET RIA Services isn't really what you need to solve your validation requirements.
The source
Get it here and remember, this may damage your health and your house is at risk if you use it as it is:
Download Source (15 KB)
UPDATE:Be sure to go on and read Part III in this series!

Post By
Josh Twist
07:51
31 Jul 2009
» Next Post:
How to work with PropertyChanged's smelly name string
« Previous Post:
New snippets for Silverlight and WPF
Comments:
Posted by
Chad
@
02 Aug 2009
00:27
Thanks for your post. Have been thinking about how to tackle this problem elegantly in my own app atm.
Posted by
Jonathan
@
06 Aug 2009
11:17
Nice example. This gets pretty close to what I was looking for but (I havent actually run your code yet) I think that when your ValidationScope calls UpdateSource() for the FrameworkElement, there could be some issues that cause it to not work properly. Specifically if you have some of your items in different TabItems in a TabControl and the user tries to save, if the user hasn't actully viewed some of the TabItems, the UpdateSource for those unviewed controls (TextBox, etc) wont do anything. I think this relates to the PropertyPathListener not being initialized in the OnAttach property within the BindingExpression class. Any ideas for this? I am almost the point of abandoning BindingExpression and just setting ToolTip on each control for my errors.
Posted by
josh
@
07 Aug 2009
04:58
That's an interesting point and I haven't tried this.
I guess the display wouldn't need to update if you can't see the control though and the important thing would be to make sure full validation occurs before update.
e.g.
PersonValidationScope.ValidateScope();
if (PersonValidationScope.IsValid() && Validator.ValidateObject(this.Person))
{
// Save changes!
}
Make sense?
Posted by
tomas.k
@
17 Aug 2009
07:31
Great article,
I looking for something similar for some time. Thank you for sharing it!
Posted by
kanur
@
17 Aug 2009
13:43
Other problem I am solving is localization of messages from ValueConverter. I am not able to localize message: "Input is not in correct format".
I tried my custom ValueConverter but there is not possible to throw exception to be caught by UI.
Any ideas?
Thanks
Posted by
Mark
@
20 Aug 2009
00:56
Hi Josh, I finally got round to implementing this. One question came about fairly quickly: how would you deal with optional fields, that need to be validated only when information is entered? Thanks, Mark
Posted by
Mark
@
20 Aug 2009
00:56
Hi Josh, I finally got round to implementing this. One question came about fairly quickly: how would you deal with optional fields, that need to be validated only when information is entered? Thanks, Mark
Posted by
Paurav
@
27 Aug 2009
14:12
Hi Josh,
Really like this solution. Thanks.
Mark, you can use a custom validator attribute on the property you are validating.
Posted by
roopesh
@
31 Oct 2009
01:35
Its really help fulll ...can we do same validation in ria services??????????????? how about custom validation?
Posted by
R4cOOn
@
18 Nov 2009
00:05
This is the only post that I found that dealt cleanly with the issue of the validation occurring in the ViewModel.
I share your belief in the "no code-behind" and I was not looking forward to adding a lot of it.
My only gripe is that I'd rather had the class separated in a Validator and a ValidationScopeBahavior in much the same way as the commands and their associated behavior are done in PRISM.
I couldn't get it to work though because I couldn't keep hold of the dependency object if the classes are separated (I could call Validate() but then the DependencyObject containing the ScopeElement wouldn't be there).
Good job!
Posted by
Aaron
@
18 Jan 2010
13:20
Nice! But I still think IsValid should be supplied to us out of the box :)
Posted by
Antti Makkonen
@
26 Jan 2010
04:54
Thanks for great sample.
I am now stuck with how to actually localize validation messages using ValueConverter or similar approach.
Any ideas?
Posted by
Ken
@
18 Feb 2010
15:18
Nice! This is the only way to go in silverlight 3. The DataForm is useless in my opinion.
Posted by
Dmitriy
@
08 Mar 2010
07:10
Thanks for the great article!
In your example the error message for the salutation combobox suppossed to be (ErrorMessage = "Salutation is required") yet it shows "Input is not in a correct format."
I ran into similar issue with validating combobox in my application. Any ideas how to overcome that?
Posted by
Nirmal
@
27 Mar 2010
06:21
Hai,
i am using radgridview. While editing the cell details i am using radGridView_CellValidating event.
In that,
void radGridView_CellValidating (object sender, Telerik.Windows.Controls.GridViewCellValidatingEventArgs e)
{
e.IsValid = false;
e.ErrorMessage ="Invalid Data(Some Message)";
}
It shows the error message when mouse over on the right top corner of tht red indication. But i need when focusing on that cell or immediate when error occurs(doesn't need tooltipservice). How can i do this?
Posted by
boldtechie
@
14 Apr 2010
12:46
Hi, I was trying the validation with datagrid in silverlight 3. But my error messages was not showing up when i hit save. Is there any thing else i have to do in code?
I debugged through code, found that just like any normal textbox control in the page the be.UpdateSource(); will be called. but my text box was not showing up the error messages when its inside datagrid.
Posted by
Eric
@
05 Jun 2010
05:26
I have the same problem as boldtechie . Are there any known problem if we use TemplatePanels and custom controls?
Posted by
TM
@
02 Aug 2010
12:31
Thank you, Josh. Really good input - just what I needed!