In this post I’m again comparing two features of Silverlight and Flex, this time we’re going to take a look at how data validation is implemented on both platforms. This will be a very interesting comparison, since Flex and Silverlight take entirely different approaches. Which one is better? I can let you know in advance that one of the two is a bit broken. Read on to find out which one……
Flex Data Validation
Let’s take a look a the following code:
1: <?xml version="1.0" encoding="utf-8"?>
2: <s:Application >="http://ns.adobe.com/mxml/2009"
3: >="library://ns.adobe.com/flex/spark"
4: >="library://ns.adobe.com/flex/halo" minWidth="1024" minHeight="768" creationComplete="application1_creationCompleteHandler(event)">
5:
6: <fx:Script>
7: <![CDATA[
8: import mx.events.FlexEvent;
9:
10: [Bindable]
11: private var _person : Person;
12:
13: protected function application1_creationCompleteHandler(event:FlexEvent):void
14: {
15: _person = new Person();
16: _person.age = 20;
17: }
18:
19: ]]>
20: </fx:Script>
21: <fx:Declarations>
22: <mx:NumberValidator domain="int" minValue="0" source="{_txtAge}" property="text"
23: lowerThanMinError="Age must be larger than 0!"
24: invalidCharError="The provided input is not a number!"
25: integerError="Age can't be a decimal number!"/>
26: </fx:Declarations>
27:
28:
29: <s:TextInput id="_txtAge" x="226" y="204" text="@{_person.age}"/>
30:
31: </s:Application>
and the following class:
1: package
2: {
3: public class Person
4: {
5: private var _age: int;
6:
7:
8: public function Person()
9: {
10: }
11:
12: public function get age():int
13: {
14: return _age;
15: }
16:
17: [Bindable]
18: public function set age(value:int):void
19: {
20: _age = value;
21: }
22:
23: }
24: }
I’m going to explain data validation in Flex as simple as possible. The first code snippet is the user interface part. You can see that this is a very simple user interface with just a TextBox in which a user can enter an age. The entered age is passed on to an instance of the Person class shown in the second code snippet. This is done by using the new two way databinding feature in Flex 4, which uses the “@” in front of the databinding expression. At startup the creationComplete event is used to initialize the Person with an age of 20:
When the user enters an invalid (negative, decimal or not a number) value the user interface changes to this:
This all happens automatically because of the NumberValidator used on line 22. Data validation in Flex is done using the Validator class and it’s different sub classes like NumberValidator. You just configure a validator with a couple of things:
- source: This is the object which holds the property you want to validate.
- property: This is the property you want to validate.
- Validator specific properties: These include error messages of the different validations a validator can do, or for example a regex when using a RegExpValidator.
- trigger: This is the object which dispatches the event that triggers validation. I don’t set this in the code above, because by default this is the same object as the source property.
- triggerEvent: The event that triggers the validation, by default this is the valueCommit event. Since the source in the example above is the TextBox and the valueCommit event is used, validation is triggered when the user presses enter or the TextBox loses focus.
There are a couple of things I really like about Flex’ approach. Firstly, these are the built in validators:
- CreditCardValidator
- CurrencyValidator
- StringValidator
- NumberValidator
- PhoneNumberValidator
- DateValidator
- EmailValidator
- RegExpValidator
- ZipCodeValidator
- SocialSecurityNumberValidator
And if those are not enough, you can create your own validator by inheriting from the Validator class and override the doValidation() method. The second thing I like is that you can easily customize the triggering of the validation:
1: <mx:NumberValidator domain="int" minValue="0" source="{_txtAge}" property="text"
2: lowerThanMinError="Age must be larger than 0!"
3: invalidCharError="The provided input is not a number!"
4: integerError="Age can't be a decimal number!"
5: triggerEvent="change"/>
By setting the triggerEvent to “change”, validation is now triggered with every button the user presses instead of when losing focus or pressing enter. There are also things that I don’t like, for example, data validation doesn’t prevent databinding. This means that even when an incorrect value is set in the TextBox, the value is still propagated to the setter of the Person class. If I did a check in the setter and threw an exception, I would have a serious problem because there is no easy place to catch the exception, except for the global error handler. Next to that, certain exceptions are swallowed by the databinding engine and others are not. Lastly, Flex’ Validator classes don’t play nice with asynchronous data validation.
Silverlight Data Validation
I’ve built the same user interface as in the Flex example. This time, three code snippets because of the code behind model Silverlight uses. First up, the XAML:
1: <UserControl x:Class="SilverlightValidation.MainPage"
2: >="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3: >="http://schemas.microsoft.com/winfx/2006/xaml"
4: >="http://schemas.microsoft.com/expression/blend/2008"
5: >="http://schemas.openxmlformats.org/markup-compatibility/2006"
6: mc:Ignorable="d"
7: d:DesignHeight="300" d:DesignWidth="400">
8:
9: <Grid x:Name="LayoutRoot" Background="White">
10:
11: <TextBox Height="23" Grid.Row="1" HorizontalAlignment="Left" Margin="114,98,0,0" Name="_txtAge" VerticalAlignment="Top" Width="120"
12: Text="{Binding Age,Mode=TwoWay,ValidatesOnDataErrors=True, ValidatesOnExceptions=True}"/>
13: </Grid>
14: </UserControl>
Next up, the code behind:
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Net;
5: using System.Windows;
6: using System.Windows.Controls;
7: using System.Windows.Documents;
8: using System.Windows.Input;
9: using System.Windows.Media;
10: using System.Windows.Media.Animation;
11: using System.Windows.Shapes;
12:
13: namespace SilverlightValidation
14: {
15: public partial class MainPage : UserControl
16: {
17: private Person _person;
18:
19: public MainPage()
20: {
21: InitializeComponent();
22: _person = new Person() { Age = 20 };
23: DataContext = _person;
24:
25: }
26:
27:
28: }
29: }
Lastly, the Person class:
1: using System;
2: using System.Net;
3: using System.Windows;
4: using System.Windows.Controls;
5: using System.Windows.Documents;
6: using System.Windows.Ink;
7: using System.Windows.Input;
8: using System.Windows.Media;
9: using System.Windows.Media.Animation;
10: using System.Windows.Shapes;
11: using System.ComponentModel;
12:
13: namespace SilverlightValidation
14: {
15: public class Person : IDataErrorInfo
16: {
17: public int Age { get; set; }
18:
19: #region IDataErrorInfo Members
20:
21: public string Error
22: {
23: get
24: {
25: return null;
26: }
27: }
28:
29: public string this[string columnName]
30: {
31: get
32: {
33: switch (columnName)
34: {
35: case "Age":
36: if (Age < 0)
37: {
38: return "Age can't be smaller than 0";
39: } else
40: {
41: goto default;
42: }
43: default:
44: return null;
45: }
46: }
47: }
48: #endregion
49: }
50: }
When you start the application it looks like:
And when you enter an invalid value:
Silverlight takes a whole different approach to data validation, in Flex validation is the responsibility of the Validator objects, in Silverlight the validation is the responsibility of the domain object to which is data bound. Of course, within this object you can delegate the responsibility as much as you like. As of Silverlight 4, we have three ways of doing data validation:
- By throwing exceptions in the setters of the property of the data bound object. The message in the exception becomes the validation error message.
- By implementing the IDataErrorInfo interface, this interface defines two members:
- An indexer, which is called by the binding engine supplying a property name, if the property with the supplied name is valid, you can return null otherwise the validation error message.
- An Error property which isn’t used by the binding engine but you can use to get a single error message for the entire object.
- By implementing the INotifyDataError interface. This interface is like the IDataErrorInfo interface, but has supports for events. With the IDataError interface the Indexer is called every time the value of the property changes through databinding. With the INotifyDataError interface, the bound object is examined for validation errors whenever you fire a specific event. This means that you can contact a server to do the validation and when you receive the response, you can fire the event. In short, INotifyDataError provides support for asynchronous data validation.
You have the specify which of the above options you want to use in the binding expression. When you take a look at line 12 in the first code snippet, you can see that I use two of above features, namely the IDataError interface and the exceptions option. If I wanted to use the INotifyDataError interface, I should have supplied the “ValidatesOnNotifyDataErrors = True” option in the binding expression. Take a look at the Person class, you can see that it implements the IDataError interface, but doesn’t throw any exceptions in a setter. So why the ValidatesOnExceptions option in the binding expression? I’ll come back to that later :).
So how does the IDataErrorInfo interface work? Every time the binding engine updates the property value, it calls the indexer on line 29 of the last code snippet afterwards, supplying the name of the property it has just updated. I’ve implemented the indexer on line 29 as if I had multiple properties. When you return a string, it’s a sign to the binding engine that a validation error has occurred and the returned string becomes the error message. When the binding engine get’s a sign that a validation error has occurred, it transitions the bound control, in this case the TextBox to the “InvalidFocusedState” or the “InvalidUnFocused” state. So the control template of a control determines how a control looks when a data validation error occurs. If you want to adjust this look, you have to retemplate the control and define your own look for the invalid states.
There are a couple of things I like about Silverlight’s data validation model. The first is that there is support for asynchronous data validation. The second thing is that this data validation model works really well in a MVVM environment, with the View having all the knowledge it needs to change itself when a data validation error occurs. If you want to customize it, just retemplate the view, while in Flex you have to write some actionscript code. The difficult thing is, where are you going to put this code in a MVVM scenario?
So for me (because I really like MVVM and asynchronous data validation) Silverlight would have been the winner. I deliberately say “would have been”, because I’m going to make a bold statement here…I think that Silverlight’s data validation system is a bit broken… There I’ve said it… but before the flaming begins, let me elaborate.
Silverlight’s broken validation system
I said that I would come back to the “ValidatesOnExceptions” option in the binding expression on line 12 in the first code snippet. The reason i have it there is that this is the only way I can get a notification when a user enters a string that can’t be converted to an integer. It turns out that Silverlight’s binding system does some pre-validation before it supplies the value to the bound object, if this validation fails, the value never reaches the bound object. This happens when I enter a string that can’t be converted to a number:
It’s somewhat logical that the binding engine does some pre-conversion. The problem is, that the message shown can’t be customized! I’ve tried a couple of possible solutions:
- Create my own IValueConverter which does the string to integer conversion and supply my own message. The problem with this approach is that exceptions in the IValueConverter don’t trigger Silverlight’s validation system, only exceptions in property setters can do that. This means that the IValueConverter can’t let the system know that validation fails. How can this be you ask? Clearly the default converter triggers the validation system? This is because the default converter throws a ValidationException, this exception get’s recognized by the binding engine and gets a special treatment so that it triggers validation. But if you want to throw this exception from your own IValueConverter, you’re in for a surprise because the constructor is internal, which essentially means that only Microsoft code can throw this exception.
- Catch the FocusLost event of the TextBox, manually set the error messages of the control and manually transition the control to an InValid state using the VisualStateManager. While you can manually transition a control to an InValid state, you can’t set a controls error messages. This “Errors” property which contains the messages for a control is an attached property from the Validation class. You guessed it…the required setter to manually set the error messages is internal.
Another example of the problem: what if an user could enter an address in a TextField in a specified format (street and number) and the bound business object expected an Address object. I would use an IValueConverter to do the conversion from string to Address, which is the whole purpose of value converters. But the converter could never ever let the user know that he / she entered the address in a wrong format. This is why the validation mechanism in Silverlight is broken and if you want to fix it, you have to resort to a whole custom solution and can’t make use of the built in InValid states all the Silverlight controls have, because you can’t specify the error message to show. This means that you have to do everything yourself for all the pre-validation that the binding engine does, if the error message doesn’t suit your needs. What can Microsoft do to fix this? Very simple:
- Make the constructor of the ValidationException public.
- Or let all exceptions in an IValueConverter trigger the validation system.
- Make the attached property setters in the Validation class public, so that when you resort to a whole custom solution, you can still make use of the built in InvalidStates.
Conclusion
Phew, This post has become a little longer than I first suspected. When I develop a Flex application, I notice that having these built in validators really speed up the development. When I develop a Silverlight application, I notice that trying to work around the quirks in the validation system slows me down to a point that I choose a whole custom solution. Don’t get me wrong, I really think that Silverlight’s validation system is on the right track: it integrates beautifully with the MVVM pattern and has support for asynchronous validation. But the issues outlined above need to be fixed. When they are fixed, I think Silverlight’s validation system could surpass Flex’ validation system, certainly in MVVM scenario’s. But for now, Flex’ data validation system is more useful to me.
One comment
Interesting post! What about DataAnnotations for doing validation in Silverlight?
Pieter