Validation Quickstart for Windows Store apps using C#, XAML, and Prism
[This article is for Windows 8.x and Windows Phone 8.x developers writing Windows Runtime apps. If you’re developing for Windows 10, see the latest documentation]
From: Developing a Windows Store business app using C#, XAML, and Prism for the Windows Runtime
Learn how to validate user input for correctness in a Windows Store business app by using Prism for the Windows Runtime. The Quickstart uses the Model-View-ViewModel (MVVM) pattern, and demonstrates how to synchronously validate data, and how to highlight validation errors on the UI by using a Blend behavior.
Download
You will learn
- How to synchronously validate data stored in a bound model object.
- How to specify validation rules for model properties by using data annotations.
- How to manually trigger validation.
- How to trigger validation through PropertyChanged events.
- How to highlight validation errors on the UI with a behavior.
Applies to
- Windows Runtime for Windows 8.1
- C#
- Extensible Application Markup Language (XAML)
Building and running the Quickstart
Build the Quickstart as you would a standard project:
- On the Microsoft Visual Studio menu bar, choose Build > Build Solution.
- After you build the project, you must deploy it. On the menu bar, choose Build > Deploy Solution. Visual Studio also deploys the project when you run the app from the debugger.
- After you deploy the project, pick the Quickstart tile to run the app. Alternatively, from Visual Studio, on the menu bar, choose Debug > Start Debugging.
When the app runs you will see a page similar to the one shown in the following diagram.
This Quickstart performs synchronous validation of data stored in a model object. The page contains three text boxes that enable you to enter your name. When you enter data into a text box and the text box loses focus, the entered data is validated. In addition, when you select the Submit button, the content of each text box is validated. To pass validation each text box must contain data consisting of letters, spaces, and hyphens. If a validation error occurs, the text box containing the invalid data is highlighted with a red border and the validation error details are displayed in red text below the Submit button.
For more info about validation, see Validating user input.
[Top]
Solution structure
The ValidationQuickstart Visual Studio solution contains two projects: ValidationQuickstart, and Microsoft.Practices.Prism.StoreApps. The ValidationQuickstart project uses Visual Studio solution folders to organize the source code into these logical categories:
- The Assets folder contains the splash screen and logo images.
- The Behaviors folder contains the behavior that is used to highlight controls that have validation errors.
- The Common folder contains the style resource dictionaries used in the app.
- The Models folder contains the model class used in the app, and a helper class that returns strings from the app's resource file.
- The Strings folder contains resource strings for the en-US locale.
- The ViewModels folder contains the view model class that is exposed to the view.
- The Views folder contains the view that makes up the UI for the app's page.
The Microsoft.Practices.Prism.StoreApps library contains reusable classes used by this Quickstart. For more info about this library, see Prism for the Windows Runtime reference. With little or no modification, you can reuse many of the classes from this Quickstart in another app. You can also adapt the organization and ideas that this Quickstart provides.
Note This Quickstart does not include any suspend and resume functionality. For a validation implementation that includes suspend and resume functionality see Validating user input.
[Top]
Key classes in the Quickstart
There are several classes involved in validation. The text boxes in the UserInfoView page bind to properties of a UserInfo model object.
The UserInfo class derives from the ValidatableBindableBase class that is provided by the Microsoft.Practices.Prism.StoreApps library. The base class contains an instance of the BindableValidator class, and uses it to invoke validation whenever a bound property changes, or when the user selects the Validate button.
The BindableValidator instance acts as the data source for validation error messages that are shown in the user interface. It is the type of the ValidatableBindableBase class's Errors property.
To perform the validation, the BindableValidator class retrieves validation rules that are encoded as custom attributes of the UserInfo object. It raises PropertyChanged and ErrorsChanged events when validation state changes.
The following diagram shows a conceptual view of the key classes involved in performing validation in this Quickstart.
[Top]
Specifying validation rules
Validation rules for data are specified in the UserInfo model class. To participate in validation the UserInfo class must derive from the ValidatableBindableBase class.
The text boxes on the UserInfoView page use compound binding path expressions such as "{Binding UserInfo.FirstName, Mode=TwoWay}". This expression associates the text box's contents with the FirstName property of the object that is returned by the UserInfo property of the page's data context. This page's data context is a UserInfoViewModel object.
The UserInfo class contains properties for storing the first, middle, and last names. Validation rules for the value of each property are specified by adding attributes to each property that derive from the ValidationAttribute attribute. The following code example shows the FirstName property from the UserInfo class.
ValidationQuickstart\Model\UserInfo.cs
private const string RegexPattern = @"\A\p{L}+([\p{Zs}\-][\p{L}]+)*\z";
[Required(ErrorMessageResourceType = typeof(ErrorMessagesHelper), ErrorMessageResourceName = "FirstNameRequired")]
[RegularExpression(RegexPattern, ErrorMessageResourceType = typeof(ErrorMessagesHelper), ErrorMessageResourceName = "FirstNameRegex")]
public string FirstName
{
get { return _firstName; }
set { SetProperty(ref _firstName, value); }
}
The Required attribute of the FirstName property specifies that a validation failure occurs if the field is null, contains an empty string, or contains only white-space characters. The RegularExpression attribute specifies that when the FirstName property is validated it must match the specified regular expression.
The static ErrorMessagesHelper class is used to retrieve validation error messages from the resource dictionary for the locale, and is used by the Required and RegularExpression validation attributes. For example, the Required attribute on the FirstName property specifies that if the property doesn't contain a value, the validation error message will be the resource string returned by the FirstNameRequired property of the ErrorMessagesHelper class. In addition, the RegularExpression attribute on the FirstName property specifies that if the data in the property contains characters other than letters, spaces, and hyphens, the validation error message will be the resource string returned by the FirstNameRegex property of the ErrorMessagesHelper class.
Note Using resource strings supports localization. However, this Quickstart only provides strings for the en-US locale.
Similarly, Required and RegularExpression attributes are specified on the MiddleName and LastName properties in the UserInfo class.
[Top]
Triggering validation explicitly
Validation can be triggered manually when the user selects the Validate button. This calls the ValidatableBindableBase.ValidateProperties method, which in turn calls the BindableValidator.ValidateProperties method.
Microsoft.Practices.Prism.StoreApps\BindableValidator.cs
public bool ValidateProperties()
{
var propertiesWithChangedErrors = new List<string>();
// Get all the properties decorated with the ValidationAttribute attribute.
var propertiesToValidate = _entityToValidate.GetType()
.GetRuntimeProperties()
.Where(c => c.GetCustomAttributes(typeof(ValidationAttribute)).Any());
foreach (PropertyInfo propertyInfo in propertiesToValidate)
{
var propertyErrors = new List<string>();
TryValidateProperty(propertyInfo, propertyErrors);
// If the errors have changed, save the property name to notify the update at the end of this method.
bool errorsChanged = SetPropertyErrors(propertyInfo.Name, propertyErrors);
if (errorsChanged && !propertiesWithChangedErrors.Contains(propertyInfo.Name))
{
propertiesWithChangedErrors.Add(propertyInfo.Name);
}
}
// Notify each property whose set of errors has changed since the last validation.
foreach (string propertyName in propertiesWithChangedErrors)
{
OnErrorsChanged(propertyName);
OnPropertyChanged(string.Format(CultureInfo.CurrentCulture, "Item[{0}]", propertyName));
}
return _errors.Values.Count == 0;
}
This method retrieves all properties that have attributes that derive from the ValidationAttribute attribute, and attempts to validate them by calling the TryValidateProperty method for each property. If new validation errors occur the ErrorsChanged and PropertyChanged events are raised for each property than contains a new error.
The TryValidateProperty method uses the Validator class to apply the validation rules. This is shown in the following code example.
Microsoft.Practices.Prism.StoreApps\BindableValidator.cs
private bool TryValidateProperty(PropertyInfo propertyInfo, List<string> propertyErrors)
{
var results = new List<ValidationResult>();
var context = new ValidationContext(_entityToValidate) { MemberName = propertyInfo.Name };
var propertyValue = propertyInfo.GetValue(_entityToValidate);
// Validate the property
bool isValid = Validator.TryValidateProperty(propertyValue, context, results);
if (results.Any())
{
propertyErrors.AddRange(results.Select(c => c.ErrorMessage));
}
return isValid;
}
[Top]
Triggering validation implicitly on property change
Validation is automatically triggered whenever a bound property's value changes. When a two way binding in the UserInfoView class sets a bound property in the UserInfo class, the SetProperty method is called. This method, provided by the BindableBase class, sets the property value and raises the PropertyChanged event. However, the SetProperty method is also overridden by the ValidatableBindableBase class. The ValidatableBindableBase.SetProperty method calls the BindableBase.SetProperty method, and then provided that the property value has changed, calls the ValidateProperty method of the BindableValidator class instance.
The ValidateProperty method validates the property whose name is passed to the method by calling the TryValidateProperty method shown above. If a new validation error occurs the ErrorsChanged and PropertyChanged events are raised for the property.
[Top]
Highlighting validation errors
Each text box on the UI uses the HighlightFormFieldOnErrors behavior to highlight validation errors. This behavior can also be used to highlight validation errors on ComboBox controls. The following code example shows how this behavior is attached to a text box.
ValidationQuickstart\Views\UserInfoView.xaml
<TextBox x:Name="FirstNameValue"
Grid.Row="2"
Text="{Binding UserInfo.FirstName, Mode=TwoWay}">
<interactivity:Interaction.Behaviors>
<quickstartbehaviors:HighlightFormFieldOnErrors PropertyErrors="{Binding UserInfo.Errors[FirstName]}" />
</interactivity:Interaction.Behaviors>
</TextBox>
The HighlightFormFieldOnErrors behavior gets and sets the PropertyErrors dependency property. The following code example shows how the PropertyErrors dependency property is defined in the HighlightFormFieldOnErrors class.
ValidationQuickstart\Behaviors\HighlightFormFieldOnErrors.cs
public static DependencyProperty PropertyErrorsProperty =
DependencyProperty.RegisterAttached("PropertyErrors", typeof(ReadOnlyCollection<string>), typeof(HighlightFormFieldOnErrors), new PropertyMetadata(BindableValidator.EmptyErrorsCollection, OnPropertyErrorsChanged));
The PropertyErrors dependency property is registered as a ReadOnlyCollection of strings, by the RegisterAttached method. The dependency property also has property metadata assigned to it. This metadata specifies a default value that the property system assigns to all cases of the property, and a static method that is automatically invoked by the property system whenever a new property value is detected. Therefore, when the value of the PropertyErrors dependency property changes, the OnPropertyErrorsChanged method is invoked.
Note The HighlightFormFieldOnErrors behavior also defines a dependency property named HighlightStyleName. By default this property is set to HighlightTextBoxStyle, but can be set to the HighlightComboBoxStyle when declaring the behavior instance.
The following code example shows the OnPropertyErrorsChanged method.
ValidationQuickstart\Behaviors\HighlightFormFieldOnErrors.cs
private static void OnPropertyErrorsChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
if (args == null || args.NewValue == null)
{
return;
}
var control = ((Behavior<FrameworkElement>)d).AssociatedObject;
var propertyErrors = (ReadOnlyCollection<string>)args.NewValue;
Style style = (propertyErrors.Any()) ? (Style)Application.Current.Resources[((HighlightFormFieldOnErrors)d).HighlightStyleName] : null;
control.Style = style;
}
The OnPropertyErrorsChanged method parameters give the instance of the control that the PropertyErrors dependency property is attached to, and any validation errors for the control. Then, if validation errors are present the value of the HighlightStyleName dependency property is applied to the control, so that it is highlighted with a red BorderBrush.
The UI also displays validation error messages below the Submit button in an ItemsControl. This ItemsControl binds to the AllErrors property of the UserInfoViewModel class. The UserInfoViewModel constructor subscribes to the ErrorsChanged event of the UserInfo class, which is provided by the ValidatableBindableBase class. When this event is raised, the OnErrorsChanged handler updates the AllErrors property with the list of validation error strings from the dictionary returned by the call to the GetAllErrors method on the UserInfo instance, as shown in the following code example.
ValidationQuickstart\ViewModels\UserInfoViewModel.cs
private void OnErrorsChanged(object sender, DataErrorsChangedEventArgs e)
{
AllErrors = new ReadOnlyCollection<string>(_userInfo.GetAllErrors().Values.SelectMany(c => c).ToList());
}
[Top]