Manish Dalal's blog

Exploring .net!

Silverlight Business Application Part 3: Validation (sync)

This is part three of Building Business Application with Silverlight series that showcases the basic building blocks of a data centric application.  

Series Link: Part 0, Part 1, Part 2

Almost all business application will have varying degree of validations. For our example, let say we have a business rule that states that Age must be a number and be greater than 0 and less than 200.

DataGrid already prevents non numeric entry in Age column, thanks to backing field of type integer. Behind the scene, DataGrid uses System.Convert by way of DataGridValueConverter to coerce values to proper type.

Try to change age to a non number, you will see that as soon as you exit cell, it reverts back to old values. This is because DataGrid tries to convert string to number and when it fails (FormatException), it reverts data back to original value. (If you examine Output window of Visual Studio Editor you will find the message: A first chance exception of type 'System.FormatException' occurred in mscorlib.dll)

You can see this by turning on two attributes on the binding, ValidatesOnExceptions and NotifyOnValidationError. Setting ValidatesOnExceptions to true tells the binding engine to create a validation error when an exception occurs and setting NotifyOnValidationError to true tells the binding engine to raise the BindingValidationError event when a validation error occurs.

Change the DataGrid declaration from

<data:DataGrid x:Name="peopleDataGrid" Grid.Row="1" />

to

<data:DataGrid 
    x:Name="peopleDataGrid"
    AutoGenerateColumns="False"
    Grid.Row="1"
    >
    <data:DataGrid.Columns>
        <data:DataGridTextColumn 
            Header="First Name"
            DisplayMemberBinding="{Binding FirstName}"
            />
        <data:DataGridTextColumn 
            Header="Last Name" 
            DisplayMemberBinding="{Binding LastName}"
            />
        <data:DataGridTemplateColumn
            Header="Age"
            >
            <data:DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock 
                        Text="{Binding Age}"
                        />
                </DataTemplate>
            </data:DataGridTemplateColumn.CellTemplate>
            <data:DataGridTemplateColumn.CellEditingTemplate>
                <DataTemplate>
                    <TextBox
                        Text="{Binding Path=Age,Mode=TwoWay,NotifyOnValidationError=true,ValidatesOnExceptions=true}"
                        />
                </DataTemplate>
            </data:DataGridTemplateColumn.CellEditingTemplate>
        </data:DataGridTemplateColumn>
        <data:DataGridTextColumn
            Header="City"
            DisplayMemberBinding="{Binding City}"
            />
    </data:DataGrid.Columns>
</data:DataGrid>

Now setup event handler for BindingValidationError on the peopleDatagrid in the page.xaml.cs file as

public Page() {
    InitializeComponent();
    this.Loaded += new RoutedEventHandler(Page_Loaded);
    //this.addButton.Click += new RoutedEventHandler(addButton_Click);
    this.peopleDataGrid.KeyDown += new KeyEventHandler(peopleDataGrid_KeyDown);
    this.deleteButton.Click += new RoutedEventHandler(deleteButton_Click);
    this.peopleDataGrid.BindingValidationError += new EventHandler<ValidationErrorEventArgs>(peopleDataGrid_BindingValidationError);
}

void peopleDataGrid_BindingValidationError(object sender, ValidationErrorEventArgs e) {
    System.Diagnostics.Debug.WriteLine(e.Error.Exception.Message);
}

F5 and run the application. Enter non numeric value in Age field.

If you examine the “Output” window, you will see that we are getting exception - Input string was not in a correct format..

image

image

We can use above event handler to provide user with more descriptive error message as well as visual feedback. Add following code to BindingValidationError event handler

void peopleDataGrid_BindingValidationError(object sender, ValidationErrorEventArgs e) {
    System.Diagnostics.Debug.WriteLine(e.Error.Exception.Message);
    if (e.Action == ValidationErrorEventAction.Added) {
        ((Control)e.Source).Background = new SolidColorBrush(Colors.Red);
        this.Dispatcher.BeginInvoke(() => HtmlPage.Window.Alert(e.Error.Exception.Message));
    } else if (e.Action == ValidationErrorEventAction.Removed) {
        ((Control)e.Source).Background = new SolidColorBrush(Colors.Red);
    }
}

Now when you try to enter string, our code gets the notification, user is presented with a alter box and color of cell changes to red to indicate error.

image

You can use Exception type and Source to differentiate between different columns and customize the message. Change BindingValidationError event handler as shown

void peopleDataGrid_BindingValidationError(object sender, ValidationErrorEventArgs e) {
    System.Diagnostics.Debug.WriteLine(e.Error.Exception.Message);
    if (e.Action == ValidationErrorEventAction.Added) {
        ((Control)e.Source).Background = new SolidColorBrush(Colors.Red);
        string message = e.Error.Exception.Message;
        if (e.Error.Exception is System.FormatException
            && "Age" == ((Control)e.Source).Tag.ToString()) {
            message = "Age must be a number between 0 and 200";
        }
        this.Dispatcher.BeginInvoke(() => HtmlPage.Window.Alert(message));
    } else if (e.Action == ValidationErrorEventAction.Removed) {
        ((Control)e.Source).Background = new SolidColorBrush(Colors.White);
    }
}

Also change xaml CellEditingTemplate for Age as follows

<data:DataGridTemplateColumn.CellEditingTemplate>
    <DataTemplate>
        <TextBox
            Text="{Binding Path=Age,Mode=TwoWay,NotifyOnValidationError=true,ValidatesOnExceptions=true}"
            Tag="Age"
            />
    </DataTemplate>
</data:DataGridTemplateColumn.CellEditingTemplate>
 
Notice addition of Tag to identify Age column. If you now try to enter non number in Age field, you get custom error message

image

Let consider domain validation for age. It should not be negative and between 0 and 200. Lets add that validation. Most logical place is setter of Age property in Person class. Change Age setter as

public int Age {
    get { return _age; }
    set {
        if (value == _age) return;
        if (value < 0 || value > 200) {
            throw new Exception("Age must be between 0 and 200");
        } 
        _age = value;
        OnPropertyChanged("Age");
    }
}

Here we are checking to make sure that age is not negative and less than 200. If not, we throw exception.

image

One alternative to disruptive alert message box is to use tooltip. Change BindingValidationError event handler as shown

private void peopleDataGrid_BindingValidationError(object sender, ValidationErrorEventArgs e) {
    if (e.Action == ValidationErrorEventAction.Added) {
        ((Control)e.Source).Background = new SolidColorBrush(Colors.Red);
        ((Control)e.Source).SetValue(ToolTipService.ToolTipProperty, e.Error.Exception.Message);
    } else if (e.Action == ValidationErrorEventAction.Removed) {
        ((Control)e.Source).Background = new SolidColorBrush(Colors.White);
        ((Control)e.Source).SetValue(ToolTipService.ToolTipProperty, null);
    }
}

Also change xaml CellEditingTemplate for Age as follows

<data:DataGridTemplateColumn.CellEditingTemplate>
    <DataTemplate>
        <TextBox
            Text="{Binding Path=Age,Mode=TwoWay,NotifyOnValidationError=true,ValidatesOnExceptions=true}"
            ToolTipService.ToolTip="Please provide Age between 0 and 200"
            />
    </DataTemplate>
</data:DataGridTemplateColumn.CellEditingTemplate>

Notice addition of ToolTipService.ToolTip to Age column. This provides user with nice help when Age goes in edit mode

image

And when business rules fails

image

It will be nice to prevent user form entering string or negative values in first place. You can do that using AttachedProperties to extend control behavior (similar to AjaxControlToolkit FilterTextboxExtender), but that’s topic for another post!

Above takes care of all sync validations that provide user with immediate feedback. However, what about async validation? We will tackle that next!

Comments

ver_bal said:

Nice!

# August 28, 2008 12:47 PM

Community Blogs said:

This is part four of Building Business Application with Silverlight series that showcases the basic building

# September 3, 2008 1:46 PM

Building Business Application with Silverlight 2 (Beta 2) - Manish Dalal's blog said:

Pingback from  Building Business Application with Silverlight 2 (Beta 2) - Manish Dalal's blog

# September 10, 2008 11:28 AM

jemiller said:

I'm basically trying the same thing, only I'm running into a problem where if I try to set the BindingValidationError event handler on the DataGrid rather than on a specific control in the CellEditingTemplate, it never gets called. The event is raised if I set the BindingValidationError event handler on the specific control within the DataGrid that is doing the editing.

# September 11, 2008 5:32 PM

jemiller said:

DataGrid seems buggy to me. When I throw an exception in the setter for the property that I'm binding to, the debugger breaks at the exception (it shouldn't as far as I know). I can hit F5 to continue. If I do that, sometimes the BindingValidationError event is raised, sometime it isn't. When it is raised, it's raised twice for some reason. I think maybe I should quit wasting my time with this and wait for the next beta or RC...

# September 12, 2008 12:15 AM

Manish Dalal's blog said:

This is part six of Building Business Application with Silverlight series that showcases the basic building

# October 9, 2008 1:17 PM

Jesse Liberty - Silverlight Geek said:

I'll be writing a set of mini-tutorials on the DataGrid that will, as the King advises, begins at the

# October 22, 2008 8:48 AM

Microsoft Weblogs said:

&#160; I'll be writing a set of mini-tutorials on the DataGrid that will, as the King advises, begins

# October 22, 2008 8:57 AM

Joe Robe said:

This does NOT work on RC. The event does not get fired on the grid, it gets fired on the text box.

Joe

# October 28, 2008 1:01 PM

Community Blogs said:

Silverlight 2 is a cross browser platform(plug-in), providing developers with a familiar .net programming

# November 12, 2008 11:57 AM

Matt Watson said:

We have released a validator control toolkit and included this great input filter service in it.

www.codeplex.com/SilverlightValidator

# November 21, 2008 5:49 PM

imd said:

((Control)e.Source).Background is not compilying. Getting the following error

Error 1 'System.Windows.Controls.ValidationErrorEventArgs' does not contain a definition for 'Source' and no extension method 'Source' accepting a first argument of type 'System.Windows.Controls.ValidationErrorEventArgs' could be found (are you missing a using directive or an assembly reference?) C:\TestSample\TestSample\SLSample\SLSample\DataBinding\DataBindingWithGrid.xaml.cs 38 29 SLSample

Can you please tell me which assembly i need to reference.

Thanks

# January 22, 2009 2:42 PM

... said:

Dies ist ein gro�er Ort. Ich m�chte hier noch einmal.

# March 8, 2009 1:17 AM

venugopal said:

please send me the working sample of this project.

my emailid is avenugopalsjcit@gmail.com

regards

venugopal

# March 18, 2009 5:36 AM

venugopal said:

hi

please send me the working sample of validation controls in silverlight

my email id is avenugopalsjcit@gmail.com

regards

venugopal

# March 18, 2009 5:40 AM

haritha said:

Hi Manish,

I am using a datagrid to display some objects.I have bound the datagrid to a list. In order to do data validation I have set both NotifyValidationException=true and BindingValidationError=true. I have set up an Binding Validation error event handler.

I have followed the same steps as per your blog.But in my code Binding Validation error event is not raised at all. The Exception is displayed in the next line to the code where I have specified to throw the exception.

1. set

2. {

3.    if (value == null || value == string.Empty)

4.    {

5.        throw new Exception("Description can't be null or empty!");

6.    }

7.    _description = value;

8.    OnPropertyChanged("Description");

9.  }

The Exception is displayed at line no 7. Can you help me out in this issue.

# July 2, 2009 2:39 AM

Graco Nautilus 3 In 1 Car Seat said:

Nice article I have enjoyed reading this and I have refrred your article to my personal listing of related sites look it over www.graconautilus3in1carseats.com/.../graco-nautilus-3-in-1-multiuse-car-seat-pink-daisy

# May 4, 2010 7:12 AM

It Ain’t You, Babe??? A Not-a-bug bug in DataGrid | Jesse Liberty - Silverlight Geek said:

Pingback from  It Ain&#8217;t You, Babe??? A Not-a-bug bug in DataGrid | Jesse Liberty - Silverlight Geek

# May 10, 2010 10:21 AM