(X) Hide this
    • Login
    • Join
      • Generate New Image
        By clicking 'Register' you accept the terms of use .

WinRT Business Apps with Prism: Client Side Validation

(1 votes)
Brian Noyes
>
Brian Noyes
Joined Jun 10, 2010
Articles:   19
Comments:   117
More Articles
1 comments   /   posted on

Tweet
 

This is part 5 in the series WinRT Business Apps with Prism.

Introduction

In this article, I am going to show you how to use the client side validation mechanisms of Prism for Windows Runtime. One challenge you face when you sit down to write a business application with WinRT is that you will probably have a fair amount of data entry in a business application. But then you will quickly discover that unlike WPF and Silverlight, which had great support for input validation through features in the Bindings and controls, WinRT has none. Sure you can write logic that detects when a property changes, evaluate some rules, and then maybe pop a message dialog in the user’s face if they input some bad data. But you will be spinning the code and the patterns for doing that all by yourself. And you won’t have any native support at the input control level for detecting and showing validation.

Because this is a primary business app scenario, the Prism team wanted to give you something more to work with. So one of the core features of Prism for Windows Runtime is a set of classes to help you do validation when the user inputs data. These classes evaluate validation rules when properties on a model or view model object change, store the error messages associated with any violated rules in the object so that you can data bind to them and display errors, and some example behaviors to make visual modifications to the bound controls when they have a validation error.

So let’s get started seeing how to use those capabilities and looking at the classes that support this in Prism.

Step 1: Add some data entry fields

The starting point for this article’s code is the completed code from Part 3 in this series. In that app, we had a little bit of data input with the selection of products and entry of a quantity, but I need a few more free form entries from a user to demonstrate input validation with a more common scenarios. So I added a Customer model type and some fields at the bottom of the Add Sales page to allow you to input customer information associated with the current order being displayed on that page. Below you can see the visual result and the XAML for those fields as a starting point.

Figure1

 

   1: <Grid Grid.Row="3">
   2:     <Grid.ColumnDefinitions>
   3:         <ColumnDefinition Width="Auto" />
   4:         <ColumnDefinition Width="Auto" />
   5:         <ColumnDefinition Width="*" />
   6:     </Grid.ColumnDefinitions>
   7:     <Grid.RowDefinitions>
   8:         <RowDefinition Height="Auto" />
   9:         <RowDefinition Height="Auto" />
  10:         <RowDefinition Height="Auto" />
  11:         <RowDefinition Height="Auto" />
  12:         <RowDefinition Height="Auto" />
  13:         <RowDefinition Height="Auto" />
  14:         <RowDefinition Height="Auto" />
  15:     </Grid.RowDefinitions>
  16:     <TextBlock Grid.Row="0"
  17:                Grid.Column="0"
  18:                Text="Name"
  19:                Style="{StaticResource SubheaderTextStyle}"
  20:                Margin="5" />
  21:     <TextBox Grid.Row="0"
  22:              Grid.Column="1"
  23:              Text="{Binding Customer.Name, Mode=TwoWay}"
  24:              Margin="5"
  25:              Width="500"
  26:              HorizontalAlignment="Left"/>
  27:     <TextBlock Grid.Row="1"
  28:                Grid.Column="0"
  29:                Text="Phone"
  30:                Style="{StaticResource SubheaderTextStyle}"
  31:                Margin="5" />
  32:     <TextBox Grid.Row="1"
  33:              Grid.Column="1"
  34:              Text="{Binding Customer.Phone, Mode=TwoWay}"
  35:              Margin="5"
  36:              Width="500"
  37:              HorizontalAlignment="Left"/>
  38:     <TextBlock Grid.Row="2"
  39:                Grid.Column="0"
  40:                Text="Address"
  41:                Style="{StaticResource SubheaderTextStyle}"
  42:                Margin="5" />
  43:     <TextBox Grid.Row="2"
  44:              Grid.Column="1"
  45:              Text="{Binding Customer.Address, Mode=TwoWay}"
  46:              Margin="5"
  47:              Width="500"
  48:              HorizontalAlignment="Left" />
  49:     <TextBlock Grid.Row="3"
  50:                Grid.Column="0"
  51:                Text="City"
  52:                Style="{StaticResource SubheaderTextStyle}"
  53:                Margin="5" />
  54:     <TextBox Grid.Row="3"
  55:              Grid.Column="1"
  56:              Text="{Binding Customer.City, Mode=TwoWay}"
  57:              Margin="5"
  58:              Width="500"
  59:              HorizontalAlignment="Left" />
  60:     <TextBlock Grid.Row="4"
  61:                Grid.Column="0"
  62:                Text="State"
  63:                Style="{StaticResource SubheaderTextStyle}"
  64:                Margin="5" />
  65:     <TextBox Grid.Row="4"
  66:              Grid.Column="1"
  67:              Text="{Binding Customer.State, Mode=TwoWay}"
  68:              Margin="5"
  69:              Width="500"
  70:              HorizontalAlignment="Left" />
  71:     <TextBlock Grid.Row="5"
  72:                Grid.Column="0"
  73:                Text="Zip Code"
  74:                Style="{StaticResource SubheaderTextStyle}"
  75:                Margin="5" />
  76:     <TextBox Grid.Row="5"
  77:              Grid.Column="1"
  78:              Text="{Binding Customer.Zip, Mode=TwoWay}"
  79:              Margin="5"
  80:              Width="500"
  81:              HorizontalAlignment="Left" />
  82:     <Button Grid.Row="6"
  83:             Grid.Column="1"
  84:             Content="Submit Order"
  85:             Command="{Binding SubmitOrderCommand}"
  86:             FontSize="20" />
  87: </Grid>

Step 2: Add a Customer Model object

Now we need a model object that those fields will bind to. And we want to express some validation rules on that object for those fields. In Prism, we chose to support the most ubiquitous and built-in way to express validation rules – DataAnnotations. If you haven’t been exposed to these, it is a set of attributes in the System.ComponentModel.DataAnnotations namespace and some supporting classes to evaluate them that let you declaratively express validation rules on properties of your model objects. DataAnnotations are automatically evaluated by a number of different parts of the .NET Framework, including ASP.NET model binding, Entity Framework, and WCF RIA Services.

Because WinRT does not automatically evaluate those attribute-based rules, we need some infrastructure code to do that for us. Also, once the rules are evaluated, we need to store the resulting errors in a way that can be easily used to display them to the user. This leads us to use the ValidatableBindableBase class from Prism as our Customer base class to provide that support, which I will drill into shortly.

The resulting Customer class definition ends up looking like this:

 

   1: using System;
   2: using System.Collections.Generic;
   3: using System.ComponentModel.DataAnnotations;
   4: using System.Linq;
   5: using Microsoft.Practices.Prism.StoreApps;
   6:  
   7: namespace HelloPrismForWinRT.Models
   8: {
   9:     public class Customer : ValidatableBindableBase
  10:     {
  11:         private string _Name;
  12:         private string _Phone;
  13:         private string _Address;
  14:         private string _City;
  15:         private string _State;
  16:         private string _Zip;
  17:         
  18:         [Required]
  19:         public string Name
  20:         {
  21:             get { return _Name; }
  22:             set { SetProperty(ref _Name, value); }
  23:         }
  24:  
  25:         [RegularExpression(@"^\D?(\d{3})\D?\D?(\d{3})\D?(\d{4})$",
  26:             ErrorMessage="Please enter a 10 digit phone number")]
  27:         public string Phone
  28:         {
  29:             get { return _Phone; }
  30:             set { SetProperty(ref _Phone, value); }
  31:         }
  32:  
  33:         public string Address
  34:         {
  35:             get { return _Address; }
  36:             set { SetProperty(ref _Address, value); }
  37:         }
  38:  
  39:         public string City
  40:         {
  41:             get { return _City; }
  42:             set { SetProperty(ref _City, value); }
  43:         }
  44:  
  45:         [StringLength(2)]
  46:         public string State
  47:         {
  48:             get { return _State; }
  49:             set { SetProperty(ref _State, value); }
  50:         }
  51:  
  52:         public string Zip
  53:         {
  54:             get { return _Zip; }
  55:             set { SetProperty(ref _Zip, value); }
  56:         }
  57:     }
  58: }

You can see the use of the Required, RegularExpression, and StringLength attributes on the Name, Phone, and State properties, respectively. In addition there is a Range attribute for numeric ranges, and then the power hook – a CustomValidation attribute that you can point to your own method to do any kind of complex custom validation that you need to.

The ValidatableBindableBase class from Prism provides several things for your class. The first is that it inherits from BindableBase itself, which encapsulates the implementation of INotifyPropertyChanged so that your object can participate correctly in data binding when properties change behind the scenes. BindableBase provides some helper methods that let you write more compact property setters like those shown in the Customer class listing where the set block just needs to call SetProperty(), passing a reference to the member variable to be set and the new value that is being set on the property. That method encapsulates checking to see if the value is the same that it was before, in which case it does nothing, or if it is different, it sets the member variable and raises the property changed event.

ValidatableBindableBase also encapsulates an instance of a class called BindableValidator. This is the brains of the validation system, in that it has a method called ValidateProperty that can be called for an object it is attached to and it will reflect on that property, find the DataAnnotation attributes, evaluate them, and put any errors for that property into a dictionary of Errors it maintains that keeps a list of errors per property (where the property name is the key in the dictionary and the value is the list of error strings). It also exposes an indexer so that you can simply index into the BindableValidator with a property name and it will return the list of error strings associated with that property.

ValidatableBindableBase exposes the underlying indexer from the BindableValidator so that it can be used in data binding, along with a GetAllErrors method if you want to display a validation summary to the user for all errors on a given object. It also overrides the SetProperty method from BindableBase so it can use that as a trigger point for calling ValidateProperty when a property is changing. And of course if you know your WinRT data binding, you know that will happen when there is a focus change off a field that is being edited.

Step 3: Expose the Customer for data binding

I added a Customer property to the AddSalesPageViewModel, but since that will be associated with the current order that is being managed by the OrderRepository, the view model simply gets the Customer out of the repository:

   1: public Customer Customer 
   2: { 
   3:     get { return _OrderRepository.Customer; } 
   4: }

Then the Repository just initializes a Customer on construction associated with the current order. Obviously in a real application there would need to be a way to clear out the current data associated with an order when it is completed or canceled to allow other orders to be placed, but this sample is not covering that use case.

   1: public interface IOrderRepository
   2: {
   3:     void AddToOrder(Product product, int quantity);
   4:     ObservableCollection<OrderItem> CurrentOrderItems { get; }
   5:     Customer Customer { get; set; }
   6: }
   7:  
   8: public class OrderRepository : IOrderRepository
   9: {
  10:     ...
  11:     public Customer Customer { get; set; }
  12:  
  13:     public OrderRepository(ISessionStateService stateService)
  14:     {
  15:         Customer = new Customer();
  16:         ...
  17:     }
  18:     ...
  19: }

 

Step 4: Display validation errors to the user

Next to each one of the input fields that have rules associated with them, I added a TextBlock to display the errors for the bound property. That TextBlock needs to be bound to the errors collection associated with that property. Since the bound object is the Customer, and it has an indexer exposed from the ValidatableBindableBase to get to those errors, what that looks like is this:

   1: <TextBlock Grid.Row="0"
   2:            Grid.Column="2"
   3:            Style="{StaticResource ErrorStyle}"
   4:            Text="{Binding Customer.Errors[Name], 
   5:                   Converter={StaticResource FirstErrorConverter}}" />

So the Text property is bound to Customer.Errors[Name], which returns the collection of errors strings that result from evaluating the rules associated with the Name property. However, since that returns a collection of strings, we need a converter that will turn that into a simple string to set the Text property. I pulled in a converter from the AdventureWorks Shopper sample application that comes with Prism, which just pulls the first error out of the collection if there are any:

   1: public sealed class FirstErrorConverter : IValueConverter
   2: {
   3:     public object Convert(object value, Type targetType, 
   4:         object parameter, string language)
   5:     {
   6:         ICollection<string> errors = value as ICollection<string>;
   7:         return errors != null && errors.Count > 0 ? errors.ElementAt(0) : null;
   8:     }
   9:  
  10:     public object ConvertBack(object value, Type targetType, 
  11:         object parameter, string language)
  12:     {
  13:         throw new NotImplementedException();
  14:     }
  15: }

The ErrorStyle that is being set simply sets the Foreground color Red and the FontSize to 20.

Step 5: Highlight the control when it has a validation error

Another thing most validation mechanisms do that you may want to support as well is to highlight the control itself in some way when it has a validation error. For example, both WPF and Silverlight put a red box around the control when the Binding sees that there are validation errors.

Because there is no built in support for that in WinRT, you have to do a little extra work. The best way to implement some code that supplements the behavior of an existing control that you do not own the code for is with a behavior. In WinRT, attached behaviors (based off of attached properties) are the way to go until we have first class support for Blend Behaviors in WinRT.

The Prism sample code has a couple of these already implemented for you. For this article, I pulled the HighlightOnErrors behavior class out of the Prism Validation QuickStart that is designed to work with a TextBox and apply a style to it when there are errors to change it’s appearance. That class looks like this:

   1: public static class HighlightOnErrors
   2: {
   3:     public static DependencyProperty PropertyErrorsProperty =
   4:         DependencyProperty.RegisterAttached("PropertyErrors", 
   5:         typeof(ReadOnlyCollection<string>), typeof(HighlightOnErrors),
   6:         new PropertyMetadata(BindableValidator.EmptyErrorsCollection, OnPropertyErrorsChanged));
   7:  
   8:     public static ReadOnlyCollection<string> GetPropertyErrors(DependencyObject obj)
   9:     {
  10:         if (obj == null) { return null; }
  11:         return (ReadOnlyCollection<string>)obj.GetValue(PropertyErrorsProperty);
  12:     }
  13:  
  14:     public static void SetPropertyErrors(DependencyObject obj, 
  15:        ReadOnlyCollection<string> value)
  16:     {
  17:         if (obj == null) { return; }
  18:         obj.SetValue(PropertyErrorsProperty, value);
  19:     }
  20:  
  21:     private static void OnPropertyErrorsChanged(DependencyObject d, 
  22:         DependencyPropertyChangedEventArgs args)
  23:     {
  24:         if (args == null || args.NewValue == null) { return; }
  25:  
  26:         TextBox textBox = (TextBox)d;
  27:         var propertyErrors = (ReadOnlyCollection<string>)args.NewValue;
  28:  
  29:         Style textBoxStyle = (propertyErrors.Count() > 0) ? 
  30:             (Style)Application.Current.Resources["HighlightTextStyle"] : null;
  31:  
  32:         textBox.Style = textBoxStyle;
  33:     }
  34: }

You can see that it declares an attached property called PropertyErrors. It is designed so you can set that property through a binding to the errors collection for a property. When the errors collection changes, it will re-evaluate its logic and set a style on the control if there are errors in the errors collection. It is hard wired to expect an application scoped resource named

So all we need to do is attach that to the TextBoxes that have rules on them with the PropertyErrors bound the to Errors collection for the same property the Text property is bound to and we are good to go:

   1: <TextBox Grid.Row="0"
   2:          Grid.Column="1"
   3:          Text="{Binding Customer.Name, Mode=TwoWay}"
   4:          Margin="5"
   5:          Width="500"
   6:          HorizontalAlignment="Left"
   7:          beh:HighlightOnErrors.PropertyErrors="{Binding Customer.Errors[Name]}" />

Now with that in place on the Name, Phone, and State field, and errors introduced on those fields, we have a display like so:

Figure2

Step 6: Validate all properties on submit

A final thing you probably want to do is make sure all errors are checked when the user tries to submit, because the validation errors do not display until the user has tried to change the field (by design) and you may have some other required fields there. Plus you probably want to prevent calling the back end or trying to persist the changes if they are invalid.

To do that I have a DelegateCommand bound to the SubmitOrder button with the following command handler:

 

   1: private void OnSubmit()
   2: {
   3:     // You can trigger all properties to evaluate their rules 
   4:     Customer.ValidateProperties();
   5:     // May want to get all errors for a validation summary display:
   6:     var allErrors = Customer.GetAllErrors().Values.SelectMany(c => c).ToList();
   7:     // You can check for any validation errors by checking the count of all errors
   8:     if (allErrors.Count == 0)
   9:     {
  10:         // Go ahead and persist
  11:     }
  12: }

You can see that the ValidateProperties() method will trigger validation across all properties on the object. Once that is done, the errors collection for each property will be populated. You can collect all errors across all properties with the GetAllErrors method and can flatten that dictionary into a list with LINQ.

Summary

In this article I have shown you how to use the validation features of Prism to support rich input validation with minimal effort. You saw that all you need to do is first have your model objects inherit from ValidatableBindableBase and have your property set blocks call SetProperty on the base. Next you add DataAnnotation attribute based rules to the properties on your object. You can then data bind to that object normally, and whenever you set a property, the data annotations rules for that property will be evaluated. To display the error strings, you can bind through the Errors indexer exposed from the base class, passing in the property that you want to get the errors for. And to display errors on the control itself, you can use a behavior to adorn that control based on the presence of errors in the same indexer.

About the Author

Brian Noyes is CTO of Solliance (www.solliance.net), a software development company offering Architecture as a Service, end-to-end product development, technology consulting and training. Brian is a Microsoft Regional Director and MVP, Pluralsight author, and speaker at conferences worldwide.  Brian worked directly on the Prism team with Microsoft patterns & practices, and has a Pluralsight course that covers Prism for Windows Runtime end-to-end titled Building Windows Store Business Apps with Prism. Brian got started programming as a hobby while flying F-14 Tomcats in the U.S. Navy, later turning his passion for code into his current career. You can contact Brian through his blog at http://briannoyes.net/ or on Twitter @briannoyes.


Subscribe

Comments

  • AmakamuKenhjiro

    Re: WinRT Business Apps with Prism: Client Side Validation


    posted by AmakamuKenhjiro on Jan 14, 2014 15:22
    Good

Add Comment

Login to comment:
  *      *       

From this series