Introduction

In this article, you see how to perform both synchronous and asynchronous form validation in a UWP app. You see how set up viewmodel properties for validation, both declaratively and programmatically. You look at how to define your validation logic in a single method or to have the validation logic reside on a remote server. You also see how to use UWP dictionary indexers to bind form validation errors to a control in your view. You then explore the innerworkings of the validation system, and see how Calcium decouples validation from your viewmodel so that it can be used with any object implementing INotifyPropertyChanged.

Background

A few years ago, I wrote about the various approaches to form validation for XAML based apps, which is in both editions of my Windows Phone Unleased books. The book chapter covers the various approaches available at the time: simple exception based validation and the more flexible INotifyDataErrorInfo interface based approach, which was introduced in Silverlight 4. During the writing of that chapter, I developed an API that makes it pretty easy not only to perform synchronous validation, but also asynchronous validation. I’ve since made the code available in the Calcium MVVM framework, which is available for WPF, Windows Phone, UWP, Xamarin Android and iOS. Just search for Calcium on NuGet, or type "Install-Package Calcium" from the package manager console. The downloadable sample contains a simple example of how to perform synchronous and asynchronous form validation for a UWP app using Calcium.

Getting Started

The Calcium framework relies on an IoC container and a few IoC registerations, for logging, loosely couple messaging, settings, and all those things you end up needing in a reasonably complex application. If you know what you’re doing, you can ‘manually’ initialize Calcium’s infrastructure. But, the easy way to initialize Calcium is to use the Outcoder.ApplicationModel.CalciumSystem class.

In the downloadable example project I began by initializing Calcium in the App.xaml.cs file, like so:

var calciumSystem = new CalciumSystem();
calciumSystem.Initialize();

The call to Initialize is performed in the OnLaunched method of the App class. The call takes place if the rootFrame is null, which indicates that the app is launching from a cold start and that CalciumSystem.Initialize has not been called before. Calcium needs to be initialized after the rootFrame is created so that it can perform navigation monitoring; which among other things allows Calcium to automatically save the state of your view models.

NOTE: If Calcium can’t find your root Frame object, then it will schedule a retry five times before giving up. The retry gives your app time to initialize.

In Calcium, you subclass the Outcoder.ComponentModel.ViewModelBase class to provide access to a host of services for your own viewmodels, such as property change notifications, state management, and of course property validation.

In the sample, the MainPageViewModel is the viewmodel for the MainPage class.

publicclass MainPageViewModel : ViewModelBase
{
…
}

MainPageViewModel contains several properties. We’ll demonstrate the validation using two properties: TextField1 and TextField2. The ViewModelBase class contains an Assign method, which automatically raises property change events on the UI thread and signals to the validation system, that a value has changed. See the following:

As an aside, several years ago, I settled on the method name ‘Assign’ rather than ‘Set’ because Set is a Visual Basic keyword. Some other frameworks use Set, but I didn't want to get tangled up with non-CLS compliance.

You nominate a property for validation by either decorating it with the [Validate] attribute, or by calling the ViewModelBase class’s AddValidationProperty method, as shown:

When a PropertyChanged event occurs, the validation system kicks into action, and calls either your overridden GetPropertyErrors method, or your overridden ValidateAsync method; depending on whether you need asynchronous support, such as when calling upon a remote server for validation.

If you only require synchronous validation, in that you don’t need to validate data remotely or using potentially high latency database calls, then override the GetPropertyErrors. See Listing 1.

GetPropertyErrors returns an IEnumerable<datavalidationerrors>, which is merged, behind the scenes, into a dictionary that can be used to display errors in your view. You see more on that in a moment.

When creating a DataValidationError you’ll notice that an integer value is associated with each error. For TextField1, in the example, 1 indicates that the ID of the error is 1. Because a field may have multiple validation errors, an ID allows us to refer specifically to a particular validation error if we need to, without relying on the error message text.

Asynchronous validation is performed in the same manner. But instead of overriding the ViewModelBase’s GetPropertyErrors method, you override the ValidateAsync method. See Listing 2.

NOTE: Overriding the ValidateAsync method causes the GetPropertyErrors method to be ignored.

In the example, we simulate a potentially long running asynchronous validation by awaiting Task.Delay. The result is returned using a ValidationCompleteEventArgs instance, which can contain either the list of validation for the specified property, or an error; indicating that validation failed for some reason.

Sometimes there is interdependency between properties and you need to validate multiple properties together. To achieve that, override the ViewModelBase class’s ValidateAllAsync method, and call the underlying DataErrorNotifier.SetPropertyErrors method for each property with errors.

Displaying Validation Errors

It’s important to display validation errors in a way that doesn’t disrupt the workflow of the user. In the sample, I display validation errors beneath each text field using a ListBox. See Listing 3. Fortunately the ListBox happily expands and contracts depending on whether it has any errors to display.

The ItemSource property of the ListBox is bound to the collection of validation errors for the associated property. A dictionary indexer is used to retrieve the collection using the property name as a key.

NOTE: Binding to Dictionary indexers is a new feature in Windows 10 Anniversary Update and won’t work in earlier versions of Windows 10. Please also note that it has some limitations. One such limitation is that binding to a custom implementation of an IReadOnlyDictionary failed in my tests. The binding infrastructure seemed to expect a concrete ReadOnlyDictionary instance. For this reason, I had to rework the DataErrorNotifier class to support binding to the ValidationErrors property. One other important limitation is that dictionary indexer only support string literals.

ProgressRing controls are used to indicate that validation is in progress. Each ProgressRing control’s IsActive property is bound to a corresponding viewmodel property.

The TextBox control only updates its binding source property when it loses input focus. Unlike WPF and other XAML implementations, TextBox doesn’t provide an UpdateSourceTrigger binding property. For that reason, I use Calcium’s UpdateSourceTriggerExtender attached property, which pushes the update through to the source property whenever the TextBox’s Text property changes.

The UpdateSourceTriggerExtender attached property works with TextBox and PasswordBox controls.

NOTE: Calcium’s UpdateSourceTriggerExtender attached property does not work with x:Bind expressions. You must use the traditional Binding markup extension.

The two arguments supplied to the submitCommand constructor is an action, IsSubmitEnabled, which determines if the button should be enabled; and a Submit action, which performs the main action of the button.

IsSubmitEnabled simply checks whether the form has any errors, like so:

bool IsSubmitEnabled(object arg)
{
return !HasErrors;
}

The Submit method validates the properties and displays a simple message using Calcium’s DialogService, as shown:

NOTE: The Calcium framework also supports the notion of asynchronous commands, but that is outside the scope of this article.

The ViewModelBase class’s ErrorsChanged event gives us the opportunity to refresh the enabled state of the submitCommand. The DelegateCommand’s RaiseCanExecuteChanged method invokes the IsSubmitEnabled method, and the IsEnabled property of the button is updated.

Behind the Scenes

When Calcium’s ViewModelBase class is instantiated, it creates an instance of a DataErrorNotifier; passing itself to the DataErrorNotifier class’s constructor. See Listing 4.

DataErrorNotifier requires an object that implements INotifyPropertyChanged, and an object that implements Calcium’s IValidateData interface. IValidateData contains a single method definition:

ValidateAsync is the method we implemented earlier, and is responsible for validating each property.

By separating the INotifyPropertyChanged owner and the IValidateData object, we effectively decouple validation from the viewmodel. So, if we liked, we could provide a completely separate validation subsystem within our application. In addition, validation is not restricted to just viewmodels. You could use the DataErrorNotifier to provide validation for any object implementing INotifyPropertyChanged.

Reading the validation attributes for the owner object involves retrieving the PropertyInfo object for each property in the class, and using the GetCustomAttributes to look for the existence of the ValidateAttribute attribute. See Listing 5. If a property is decorated with a Validate attribute it is added to the list of validated properties.

If you choose to add your properties using the AddValidationProperty method, then a delegate is created using the MethodInfo object’s CreateDelegate. See Listing 6. Creating a delegate rather than relying on the PropertyInfo object’s GetValue method may improve performance. This is because retrieving or setting a value via reflection is notoriously slow. Please note, however, that I’ve yet to implement that for the Validate attribute.

Before BeginGetPropertyErrorsFromValidator returns its result, the ValidationCompleteEventArgs object is passed to the ProcessValidationComplete method, which adds any resulting validation errors to the errors collection via the SetPropertyErrors method. See Listing 8.

SetPropertyErrors populates an ObservableCollection<DataValidationError> with the validation errors. See Listing 9. An ObservableCollection is used because it makes displaying validation changes in the UI a snap. You need only bind to the property name in the ValidationErrors dictionary, as we saw back in Listing 3.

Conclusion

In this article, you saw how to perform both synchronous and asynchronous form validation in a UWP app. You saw how set up viewmodel properties for validation, both declaratively and programmatically. You looked at how to define your validation logic in a single method or to have the validation logic reside on a remote server. You also saw how to use UWP dictionary indexers to bind form validation errors to a control in your view. You then explored the innerworkings of the validation system, and saw how Calcium decouples validation from your viewmodel so that it can be used with any object implementing INotifyPropertyChanged.

I hope you find this project useful. If so, then please rate it and/or leave feedback below.

Share

About the Author

Daniel Vaughan is a seven time Microsoft MVP and co-founder of Outcoder, a Swiss software and consulting company dedicated to creating best-of-breed user experiences and leading-edge back-end solutions, using the Microsoft stack of technologies--in particular Xamarin, WPF, and the UWP.

Daniel is the author of Windows Phone 8 Unleashed and Windows Phone 7.5 Unleashed, both published by SAMS.

Daniel is the developer behind several acclaimed mobile apps including Surfy Browser for Android and Windows Phone. Daniel is the creator of a number of popular open-source projects, most notably Calcium.

Would you like Daniel to bring value to your organisation? Please contact