Data Validation And Templating in Silverlight 4

MiniTutorial

This is the fifth in a series on Templates and DataValidation.
[ First In Series Previous In Series ]

In this and the next posting I will create an application that will demonstrate how to modify the Visual State for invalid data. Along the way we will review:

  • Binding form elements to data
  • The binding engine and MVVM
  • Using the binding engine for data validation
  • Data validation and the Visual State Manager
  • Using the MVVM Light Toolkit
  • Best practices in application development
  • Creating Child Windows

The project we’ll build will be based on the project built in this video about asynchronous data validation, but we will modify and extend it a bit.

Probably the biggest structural change is that we’ll implement this as a MVVM project.  Here are a couple good quick references on MVVM if this is new for you:

MVVM – It’s Not Kool-Aid
Video: MVVM Introduction

Note that this example assumes you have MVVM Light installed. This is a free library, and while I don’t usually add an external library, the simple fact is that writing MVVM by hand when you could use a library is an absurd waste of energy. As my buddy David Platt has said (paraphrasing): You can do an appendectomy through your mouth, but it takes longer and it hurts more.

In this first approach, we’ll create the MVVM project as well as the editing form and we’ll populate it with data from an object.  To get started, open Expression Blend 4 (or Visual Studio 2010)  and select MvvmLight (SL4)  Name the project Validation and make sure Language is set to C# (or VB if you are transposing) and the Version is set to 4.0.  Click OK.

Layout and Prompts

DataValidationForm Our design calls for an outer grid to hold the entire form, and an inner grid to hold the data gathering elements.  The outer grid has two rows and no columns defined,

<Grid.RowDefinitions>
   <RowDefinition
      Height="Auto" />
   <RowDefinition
      Height="Auto" />
</Grid.RowDefinitions>

The inner grid has 8 rows and two columns, with the first column is 1/2 the size of the second (that is, set the first column’s width to 1* and the second column’s width to 2*).

[ For this and all images, click on the image to see it full size ]

Add the seven prompts to the rows:

  • First Name
  • Last Name
  • Street Address
  • City, State Zip
  • Renewal Date
  • Yearly Fee

On the seventh row add a TextBlock that spans both columns, is flush left and either has no text or says “Ready…”

The final row has a button that says “Show Log”

Using Styles and Resources

Because you are using styles, all of the prompts will be identical , except for the text to display and the row:

<TextBlock
   Text="First Name"
   Grid.Row="0"
   Grid.Column="0"
   Style="{StaticResource PromptStyle}" />
<TextBlock
   Text="Last Name"
   Grid.Row="1"
   Grid.Column="0"
   Style="{StaticResource PromptStyle}" />
<!-- etc. –>

Data Entry

Let’s turn now to the data entry fields. For these, we need a few more styles, familiar from the previous example, so I’ll just remind you that TextBoxStyle sets TextWrapping to on and the Width to 150 and the Height to Auto.  It is based on InputControlStyle which sets the FontFamily to Georgia, the FontSize to 10 and the FontWeight to Normal, and InputControlStyle is in turn based on FWEStyle which can be applied to any Framework Element and sets a Margin of 5 all aound, and sets the Horizontal and Vertical Alignment to Right and Bottom respectively.]

You can see in the illustration above that all of the input controls are TextBoxes except for the one used for Renewal Date, which is of type DatePicker.  This last is not part of the core Silverlight control set, and so you will need to create a namespace for System.Windows.Controls

xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"

DataBinding

With the form created we can go about the business of obtaining data and binding properties of that data to the form.  I have delegated responsibility for obtaining the data (or, more accurately, pretending to obtain the data) to the WebServiceMock class in my Model folder.

Here is the mock web service, which simply uses a timer to create a delay and then “flips a coin” to return an answer on whether or not the “submitted” data is valid.

using System;
using System.Collections.Generic;
using System.Windows.Threading;

namespace DataValidation.Model
{
   public class WebServiceMock
   {
      private const int MinDelay = 10;
      private const int MaxAddedDelay = 10;
      private static readonly Random RandomGenerator = new Random();

      private static readonly Dictionary<DispatcherTimer, ViewModel.EditCustomerViewModel.CustomerCallBack> CallBacks =
          new Dictionary<DispatcherTimer, ViewModel.EditCustomerViewModel.CustomerCallBack>();

      public static void StateTest(
          string state,
          ViewModel.EditCustomerViewModel.CustomerCallBack callBack )
      {
         var timer = new DispatcherTimer
         {
            Interval = new TimeSpan(
               0,
               0,
               0,
               MinDelay +
               RandomGenerator.Next( MaxAddedDelay ),
               0 )
         };
         timer.Tick += TimeElapsed;
         CallBacks.Add(
             timer,
             callBack );
         timer.Start();
      }

      private static void TimeElapsed(
          object sender,
          EventArgs e )
      {
         var t = sender as DispatcherTimer;
         if ( t == null )
            return;

         t.Stop();
         var custCallBack = CallBacks[ t ];

         var isValid = RandomGenerator.Next() % 2 == 0;
         custCallBack( isValid );
      }

   }
}

the interesting thing to note about this code is that it creates a dictionary that uses a DispatchTimer as the key and an EditCustomerViewModel.CustomerCallBack as the value.  This is a generic solution to the problem of creating an unknown number of callbacks that can be accessed by using the particular timer that “fired.” It is overkill for this example, but since I had it “lying around” it was easier to just use it as is.

The Model

The other occupant of the Model folder is Customer.cs, which defines the rudimentary customer object, but not any of the rules for data validity for customers.  Rules for how customers will respond to and be displayed in the UI belong in the ViewModel, which we will cover below.  Here is the entire contents of Model.Customer:

using System;

namespace DataValidation.Model
{
   public class Customer
   {
       public string FirstName { get; set; }
       public string LastName { get; set; }
       public string Street { get; set; }
       public string City { get; set; }
       public string State { get; set; }
       public string Zip { get; set; }
       public DateTime RenewalDate { get; set; }
       public Decimal YearlyFees { get; set; }
       public string Email { get; set; }

       public Customer(
          string firstName,
          string lastName,
          string street,
          string city,
          string state,
          string zip,
          DateTime renewalDate,
          Decimal yearlyFees,
          string email )
       {
           FirstName = firstName;
           LastName = lastName;
           Street = street;
           City = city;
           State = state;
           Zip = zip;
           RenewalDate = renewalDate;
           YearlyFees = yearlyFees;
           Email = email;
       }
   }
}

The View Model

Mediating between the Model and the View, is the ViewModel.

For this posting, I assume you’ve read at least MVVVM – It’s Not Kool-Aid and understand the basic role of the ViewModel and the relationship between databinding and MVVM

The key job of EditCustomerViewModel is to provide properties to which the controls in the form will be bound.  Thus, you will find public properties that correspond to an individual form field and a property of a Customer (e.g., there will be a FirstName field on the form, and a FirstName public property in the VM and, as well a FirstName property in the Customer.)  The relationship could of course be made more complex and less isomorphic (that is, you could have a FullName field in the form, a FirstName and LastName field in the VM and a Customer in the Model that has many other fields, including a Person base class), but there is no need to muddy the waters

We will also ask the View Model to take on some additional responsibilities:

  • Informing the view when to change its status ( in progress, completed, error, ok ) while data is checked against both the internal rules and policies and against server based rules accessed via a web service
  • Informing the view when the Log should be updated, and passing along a string for that purpose
  • Informing the view when the underlying data has changed and the view must be updated

All of this will be accomplished using the standard binding mechanism, including implementing (indirectly) INotifyPropertyChanged)

Finally, we’ll look at various ways to communicate errors to the View, including throwing exceptions, implementing INotifyDataErrorInfo and handling error checking asynchronously.

Once all of that is done, in the next tutorial, we’ll look at how to change the display of the error via the Visual State Manager.

Data Binding

The first step in databinding is to mark the databound controls with the binding syntax:

<TextBox
   x:Name="FirstName"
   Grid.Column="1"
   Text="{Binding Path=FirstName, Mode=TwoWay,
           ValidatesOnExceptions=true, NotifyOnValidationError=true}"
   Style="{StaticResource TextBoxStyle}" />

The Binding property here has four attributes: Path, Mode, ValidatesOnException and NotifyOnValidationErrors.  We’ll look at the latter two in just a bit, the first two are used to indicate the name of the property to bind to and whether the binding is OneTime, OneWay or TwoWay.  TwoWay binding allows for the entries in the UI to update the underlying data, OneWay is display only, and OneTime is for setting once never to be changed.

The View Model Classes

Let’s start with the classes needed to provide binding and validation support for the Customer…

public class EditCustomerViewModel :
     ViewModelBase,
     INotifyDataErrorInfo

ViewModelBase The declaration indicates that our class derives from ViewModelBase (provided by MVVMLight) and implements INotifyDataErrorInfo (to which we’ll return later).

Since ViewModelBase is in a foreign library the best way to examine it is in Visual Studio’s Object browser (right click on it and choose “Go to declaration.”   Note that in this case, the source code is available so you are free to examine the code in detail, but the Object Browser provides exactly the information we need. We see in the left pane that ViewModelBase implements INotifyPropertyChanged and, in the right pane we see that we can access the associated event (PropertyChanged) directly or, even better, indirectly through RaisePropertyChanged or RaisePropertyChanged<T>.

For now I’m going to skip over all the members that support error handling and logging and begin at the constructors. I’ve created two, one that takes the individual values of a customer, and one that takes a customer object. They do the same thing, which is to initialize the public properties of the VM class:

public EditCustomerViewModel(
  string firstName,
  string lastName,
  string street,
  string city,
  string state,
  string zip,
  DateTime renewalDate,
  Decimal yearlyFees,
  string email )
{
    FirstName = firstName;
    LastName = lastName;
    Street = street;
    City = city;
    State = state;
    Zip = zip;
    RenewalDate = renewalDate;
    YearlyFees = yearlyFees;
    Email = email;
}

public EditCustomerViewModel(Customer customer)
{
    if ( customer == null )
    {
        throw new ArgumentNullException
            ( "customer",
              "EditCustomerViewModel passed a null customer" );
    }
    FirstName = customer.FirstName;
    LastName = customer.LastName;
    Street = customer.Street;
    City = customer.City;
    State = customer.State;
    Zip = customer.Zip;
    RenewalDate = customer.RenewalDate;
    YearlyFees = customer.YearlyFees;
    Email = customer.Email;
}

All of the data that will be represented in the UI is made available as public properties. The pattern we’ll be using is this:

  • Some data in the Customer class may not be in the UI in which case it will be ignored or handled automatically in the EditCustomerViewModel. That is now shown in this example
  • Some data is not validated, and will be implemented with automatic properties (e.g., RenewalDate, Street, YearlyFees).
  • The remaining data is validated, and so will have a getter that returns the value and a setter that
    • checks whether the data is valid
    • If the data is valid sets the value of the underlying private varaibles
    • Supports INotifyProperty indirectly by calling the base class method

Variations on Data Validation

As mentioned above, we’ll be examining three distinct ways to handle data validation.

  • Raising Exceptions
  • Implementing IDataErrorInfo
  • Implementing INotifyDataErrorInfo to support asynchronous (host-based) data validation  (next posting)

Errors With Exceptions

In earlier versions of Silverlight, the steps for handling data validation errors (still valid, if somewhat deprecated today) were:

  • pass the data to the object (or in our case the view model)
  • if it is not valid, throw an exception
  • Set the property ValidatesOnExceptions to true in the Xaml for the input element in the form
  • Set the property NotifyOnValidationError to true in the Xaml for the input element in the form
  • Set TwoWay binding on the input element in the form
  • Either the Binding Engine’s type converter or the source object’s setter function throws an exception, passing along a string to be displayed by the Visual State Manager

Let’s implement the validation test for the Email property using Exceptions….

private string email;
public string Email
{
    get { return email; }
    set
    {
        if ( string.IsNullOrEmpty( value ) )
        {
            return;
        }

        if ( !value.Contains( "@" ) )
        {
           throw new Exception( "Not a valid email address" );
        }
        email = value;
        RaisePropertyChanged( "Email" );
    }
}

We start by checking to ensure that the value passed in for the new email is neither blank nor null (you can certainly change this if a blank value is valid in your application).  We then check to see if we’ve been passed an invalid email address, defined as a string of characters absent the at-sign (@).  If it is invalid, we throw the exception.  Otherwise, we set the underlying email property to the new value and call MVVMLight’s RaisePropertyChanged method with the name of the property, which will in turn call the PropertyChanged event.

Because the Email form field was set with the necessary properties: Binding mode is TwoWay and the two interfaces ValidatesOnExceptions and NotifyOnValidationErrors are set to true, if an email address is entered that does not have an at-sign, an exception will be thrown, and that exception will be caught by the error handling system, and passed to the Visual State manager for error handling.

<TextBox
   x:Name="Email"
   Grid.Row="6"
   Grid.Column="1"
   Style="{StaticResource TextBoxStyle }"
   Text="{Binding Path=Email, Mode=TwoWay,
           ValidatesOnExceptions=true, NotifyOnValidationError=true}" />


The exception is caught and passed to the Visual State Manager as a Validation error, as shown here:

NotValidEmail

Logging and Status

Before we look at IDataErrorInfo, we need to take a slight tangent to cover the Logging and Status mechanisms I added to keep the user informed about what is happening in this demo code.

Logging

Each logged item will be added to the list box in the view, through the LogEntries collection which will serve as the bound ItemsSource property.

private ObservableCollection<string> logEntries = new ObservableCollection<string>();
public ObservableCollection<string> LogEntries
{
    get { return logEntries;  }
}

It is important to use an ObservableCollection as this will be bound to the ItemsSource property of the ListBox, which automatically invokes the CollectionChanged event handler when the ListBox’s items collection changes if it is bound to an ObservableCollection (but not if, for example, it were bound to a simple List<String>).

When the ViewModel wants to record something to the log it will pass a string to the private AddLogItem method,

private void AddLogItem(string item)
{
    if ( string.IsNullOrEmpty( item ) )
        return;
    logEntries.Add( item );
}

The View’s Log ListBox will be automagically updated through the CollectionChanged event.

Status

As the user’s entries are validated, we’ll want to update the UI to indicate if there is a problem or not, or if we are waiting for validation to return from the web service. We begin by declaring an enumeration:

public enum Status
{
InProgress,
Completed,
Error,
Ok
}

InProgress and Completed are used in the asynchronous error checking covered below. The TextBlock on the last line is bound to a property of the ViewModel,

<TextBlock
   x:Name="Message"
   Text="{Binding Path=ErrorCheckingStatus, Mode=OneWay}"
   Grid.Row="7"
   Grid.Column="0"
   Grid.ColumnSpan="2" />

Notice that the binding is “OneWay,” as this text is sent from the VM but never updated by the user (a TextBlock is inherently read only).

The TextBlock is bound to the ErrorCheckingStatus property of the VM,

private Status errorCheckingStatus = Status.Ok;
public Status ErrorCheckingStatus
{
    get { return errorCheckingStatus; }
    set
    {
        errorCheckingStatus = value;
        RaisePropertyChanged( "ErrorCheckingStatus" );
    }
}

When the VM wants to update the view with a new status, it changes the value of this property, In the following code, the status is changed to Error, the Log is updated with what has gone wrong, and an exception is thrown to invoke the data validation error handling,

AddLogItem( "Invalid email address, throwing exception" );
ErrorCheckingStatus = Status.Error;
throw new Exception( "Not a valid email address" );

IDataErrorInfo

While this validation with exceptions certainly works, there are many object oriented developers who argue that it makes for very confusing code when exceptions are used other than for the semantics for which they were originally intended.

They argue specifically that exceptions were invented for those conditions that we know might happen but which we cannot reliably predict, such as running out of memory.  To use exceptions for other purposes (such as a fast way to unwind the stack or, in this case, to pass a validation error to an error handler) distorts the semantics of exceptions and makes for less intuitive code that jumps unpredictably (the dreaded spaghetti code).

WPF had a mechanism, IDataErrorInfo, that solves this problem and which was brought into Silverlight 4.  Interestingly, Silvelright 4 also introduced the interface INotifyDataErrorInfo  for asynchronous validation which has not yet made it into WPF.

.

Move Data Validation Out Of The View Model?

Folks involved with unit testing, patterns and best practices have argued that validation should not be inside the ViewModel and should be in a dedicated class included using MEF

Doing so would allow you to support Bertrand Meyer’s well established  Open/Closed Principle which asserts that code is more manageable if it is “open for extension but not for modification” – that is behavior modification should not require changes to the source code of the existing code.

See Jag Reehal’s article Applying the Open Closed Principle In Silverlight and WPF Using MEF in his excellent blog Arrange Act Assert

This pattern is not implemented here, as doing it right requires MEF which is beyond the scope of this tutorial.

To see how IDataErrorInfo works, let’s look at the validation of the Ziip code field.

public string Zip
{
    get { return zip; }
    set
    {
        var zipErrors = new List<string>();
        // Full test would at least allow for extended zip code
        // and for non-US postal codes
        // and for numerals only - a regular expression would be useful
        if ( value == null || value.Length != 5 )
        {
            const string info = " Zip code must be five characters";
            zipErrors.Add( info );
            ManageErrors(
                    "Zip",
                    zipErrors,
                    true );
        }
        else
        {
            ManageErrors(
                "Zip",
                null,
                false );

            zip = value;
            RaisePropertyChanged( "Zip" );
        }
    }
}

We do a pretty simple test for the value passed in (it must not be null and it must be 5 characters – see comments for why this is barely adequate.  If it is not valid, we add an error string to an array of strings and pass that to the helper method ManageErrors, along with the name of the property and the boolean value set to true to indicate that this is an error. Otherwise, we still call ManageErrors, but we pass in false for the boolean and a null array of error messages.

private void ManageErrors(
    string property,
    List< String > errors,
    bool addingAnError )
{
    var raiseEventErrorsChanged = addingAnError;

    if ( property != null )
    {
        if ( currentErrors.ContainsKey( property ) )
        {
            AddLogItem( "Clearing errors & invoking errorsChanged for " + property );
            currentErrors.Remove( property );
            raiseEventErrorsChanged = true; // if we did remove it, we must raise the event
        }

        if ( addingAnError )
        {
            AddLogItem( "Writing error for " + property + " and calling status status change" ); // logging
            currentErrors.Add(
                property,
                errors ); // INotifyDataErrorInfo
            ErrorCheckingStatus = Status.Error;
        }
        else
        {
            AddLogItem( "Set Status OK" );
            ErrorCheckingStatus = Status.Ok;
        }

        // if the flag is set, see if anyone has registered (!= null)
        // and raise the event
        if ( raiseEventErrorsChanged && ErrorsChanged != null )
        {
            AddLogItem( "Invoking DataErrorsChangedEventArgs with property: " + property );

            ErrorsChanged(
                this,
                new DataErrorsChangedEventArgs( property ) );
        }
    }
}

Next Steps

In the next tutorial I’ll look at handling asynchronous error checking and then we’ll take a look at modifying the UI for error display provided by the Visual State Manager.

Share

About Jesse Liberty

Jesse Liberty is a Master Consultant for Falafel Software, and has three decades of experience writing and delivering software projects. He is the author of 2 dozen books and multiple Pluralsight courses, and has been a Technical Evangelist for Telerik and for Microsoft, a Distinguished Software Engineer for AT&T, a VP for Information Services for Citibank and a Software Architect for PBS.
This entry was posted in Mini-Tutorial, Patterns & Skills, Styles and Templates and tagged , , . Bookmark the permalink.

10 Responses to Data Validation And Templating in Silverlight 4

  1. What’s up everyone, it’s my first go to see at this web site,
    and post is really fruitful in favor of me, keep up posting such
    posts.

  2. This is my first time pay a visit at here and i am in fact happy to read everthing
    at alone place.

  3. trojan zbot says:

    I think the admin of this web page is genuinely working hard in favor of his site,
    because here every material is quality based stuff.

  4. Marco says:

    I think the admin of this website is actually working hard in support of his website, since here
    every stuff is quality based material.

  5. Cleta says:

    Very nice post. I just stumbled upon your weblog and wished
    to say that I have really enjoyed browsing your blog posts.
    In any case I’ll be subscribing to your rss feed and I hope you write again soon!

  6. Thanks for some other wonderful article. Where else could anyone get that type of information
    in such an ideal means of writing? I’ve a presentation subsequent week, and I’m
    on the look for such information.

  7. Its lіke уou гead my mіnd! You seem to know a
    lot аbout thіs, like уоu wгote the book in it
    or something. I thіnk that you сould do with some pics tо dгive the messagе home a little bit, but
    otheг than that, this is excellent blog. An eхcellent
    read. I ωill certainly be back.

  8. Anonymous says:

    hgjg

  9. Pingback: Validaci

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>