Prism for Windows Runtime: Validating User Input
This is the fifth post in a series that walks you through creating a simple Windows Store app using the Prism for Windows Runtime library. Please review the fourth post in the series for the steps necessary to create the basic app based on MvvmAppBase.
One feature that is missing from Windows Store app sdk is control support for user input validation. In WPF and Silverlight, you would expect to implement INotifyDataErrorInfo/IDataErrorInfo and controls such as TextBox and ComboBox would show an error state when appropriate. Unfortunately, these controls do not support these interfaces in the current release of .NET for Windows Store apps and Windows Runtime.
What is provided is support for DataAnnotations attributes. The Prism.StoreApps library provides some assistance in validating model objects that leverage these DataAnnotationson attributes. You will need to provide your own attached properties to appropriately style input controls. You can find an example error highlighting attached property in the AdventureWorks reference implementation (HighlightFormFieldOnErrors.PropertyErrors).
You can get the latest source on CodePlex.
Let’s take a look at how you can use the Microsoft.Practices.Prism.StoreApps library to validate user input.
Complete the Creating a basic implementation of the MVVM pattern walk through from the previous post.
Add a model class to the project: Add a class named Credential.
Use ValidatableBindableBase as the base class for the Credential class.
Add two string properties to the Credential class: Username and Password.
Make sure to use a backing field and the SetProperty method provided by ValidatableBindableBase. The SetProperty method not only checks that the new value is different from the existing value before raising the PropertyChanged event, it also runs the validation logic.private string _username; private string _password; public string Username { get { return _username; } set { SetProperty(ref _username, value); } } public string Password { get { return _password; } set { SetProperty(ref _password, value); } }
Add “Required” and “StringLength” DataAnnotations attributes to each property.
[Required] [StringLength(10)] public string Username { get { return _username; } set { SetProperty(ref _username, value); } } [Required] [StringLength(20)] public string Password { get { return _password; } set { SetProperty(ref _password, value); } }
The model class is ready so let’s expose an instance of it on the SignInFlyoutViewModel.
Add a class named SignInFlyoutViewModel to the ViewModels folder.
Add a property to the SignInFlyoutViewModel that exposes the Credential model.
public class SignInFlyoutViewModel { public SignInFlyoutViewModel() { UserCredential = new Credential(); } public Credential UserCredential { get; set; } }
Use the ViewModelLocator.AutoWireViewModel=”True” attached property on the SignInFlyout page to associate this page (view) with the SignInFlyoutViewModel.
Next, let’s add some controls to the view to bind to the Username and Password properties of the view model’s Credential property. We’ll also add some TextBlocks that will bind to any validation error messages.
<TextBox Text="{Binding UserCredential.Username, Mode=TwoWay}" BorderBrush="Black" /> <TextBlock Text="{Binding UserCredential.Errors[Username], Converter={StaticResource FirstErrorConverter}}" Foreground="Red" /> <TextBox Text="{Binding UserCredential.Password, Mode=TwoWay}" BorderBrush="Black" /> <TextBlock Text="{Binding UserCredential.Errors[Password], Converter={StaticResource FirstErrorConverter}}" Foreground="Red"/>
Note that the TextBlock controls bind to the Errors property of the UserCredential on the view model. The Errors property is provided by ValidatableBindableBase and is populated when a property fails validation. Since The Errors property returns a collection of string error messages for a given property name, we will use a value converter to show only the first message. Here is the source code for the FirstErrorConverter:
public sealed class FirstErrorConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, string language) { ICollection<string> errors = value as ICollection<string>; return errors != null && errors.Count > 0 ? errors.ElementAt(0):null; } public object ConvertBack(object value, Type targetType, object parameter, string language) { throw new NotImplementedException(); } }
To use this value converter as a static resource, remember to reference it in App.xaml:
<ResourceDictionary> ... <converters:FirstErrorConverter x:Key="FirstErrorConverter" /> </ResourceDictionary>
Run the app and tap the “ShowFlyout” button. Enter some values in the two textboxes on the SignInFlyout. If you enter a string value that is too long for the field, or you remove the contents of either field, you will see the appropriate message in the TextBlocks.
Next, add a button that calls UserCredential.ValidateProperties() that validates all properties of the model object instance.
public SignInFlyoutViewModel() { UserCredential = new Credential(); ValidateAll = new DelegateCommand(() => UserCredential.ValidateProperties()); } public Credential UserCredential { get; set; } public ICommand ValidateAll { get; set; }
Add a button to the view that binds to the ValidateAll command. Remember that the background of the flyout is white according to the UI Guidelines so you will need to style your controls accordingly.
<Button Command="{Binding ValidateAll}" Content="Validate All" Foreground="Black"/>
Comments
Anonymous
May 09, 2013
Is there a way to override the ValidateProperties? Meaning, what if I had one specific property that needed to check some logic besides just the basics ( i.e. range, length, etc. ) and all other properties did the basic check? Example: Say the one property needed to check if the value entered was already used for something else. I would need to go look that up during the validation and then come back with an error message if so. ThanksAnonymous
May 10, 2013
Take a look at the AdventureWorks Shopper project. We validate the zip code field on the server, comparing it to the allowable zip codes in the user selected state. If the server side validation fails, the error is sent to the client and merged into the errors collection of the model object.Anonymous
May 10, 2013
Thanks, I will review.