Custom object validation in WinRT Universal Apps
I mentioned about a month ago that i was going to work on a task management app. Since the app is going to target both Windows Phone 8 and Windows 8, I thought it would make sense to make use of Microsoft's new Universal App set up. Unfortunately for me, this turned out to be a bigger challenge than I anticipated to get set up. I'll blog about the difficulties of setting the project up initially later, since it deals largly with Universal apps working with Prism, Unity and Mocking. Today, I'm going to go over one of the major things that the WinRT shared library is missing. Data Validation.
The big brother framework (WPF) contains an excellent set of tools for data validation. As does the Silverlight API. Microsoft has a lot of examples of working data validation in WinRT targeting Windows 8, but those all go out the door with a universal app supporting Windows Phone 8. So I built my own.
There are a number of ways you can approach this. To work through these examples, I have put together the following Account Creation View that the examples I show will be based on.
The XAML required to re-produce this is really straight forward. I centered a stack panel in a Grid row, put a couple TextBlocks and a couple TextBoxs along with a single Button. To keep things short on the blog, I will just provide the XAML required to display the account creation StackPanel.
< StackPanel Grid.Row="1"
VerticalAlignment="Center"
HorizontalAlignment="Center">
< !-- E-Mail address input -->
< TextBlock Text="Email"
Style="{StaticResource TitleTextBlockStyle}" />
< TextBox x:Name="EmailTextBox"
Margin="0 5 0 0"
MinWidth="200"
Text="{Binding Path=Email, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
< TextBlock x:Name="EmailValidationErrorTextBlock"
Foreground="Red" />
< !-- Password input -->
< TextBlock Text="Password"
Margin="0 30 0 0"
Style="{StaticResource TitleTextBlockStyle}" />
< TextBox x:Name="PasswordTextBox"
Margin="0 5 0 0"
MinWidth="{Binding ElementName=EmailTextBox, Path=MinWidth}"
Text="{Binding Path=Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
< TextBlock x:Name="PasswordValidationErrorTextBlock"
Foreground="Red" />
< !-- Login command button -->
< Button Content="Create Account"
Margin="0 10 0 0" />
</StackPanel>
Next, we need to create a model. The Model is simple and looks like this:
public class User : INotifyPropertyChanged
{
private string email;
private string password;
public event PropertyChangedEventHandler PropertyChanged;
public string Email
{
get { return email; }
set
{
email = value;
this.OnPropertyChanged("Email");
}
}
public string Password
{
get { return password; }
set
{
password = value;
this.OnPropertyChanged("Password");
}
}
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Now we are ready to go.
Code-Behind
The first option available for performing validation is doing it in the code-behind using an event handler. We can modify our Button XAML to include an event handler like this:
< Button Content="Login"
Margin="0 10 0 0"
Click="Button_Click" />
Now each time the button is called, it will fire the event. If you're a WPF developer, or have been doing Windows 8 development for awhile, you're probably yelling "This isn't WinForms!". I hear your shouts and agree, but for the sake of demonstrating a possible approach, let's follow this through to the end.
Next we need to write the validation in the code-behind. Since we are not maintaining view state, or using an ICommand implementation, we don't need to set up a View Model for this scenario. Binding to the Model is just fine. We just need to set it as our DataContext via the View's constructor.
public LoginPage()
{
this.InitializeComponent();
this.DataContext = new User();
}
Now we implement our event handler for the Create User button being clicked. This will handle all of our validation.
private void Button_Click(object sender, RoutedEventArgs e)
{
User user = this.DataContext as User;
if (user.Password.Length < 8)
{
this.PasswordValidationErrorTextBlock.Text = "Password must be a minimum of 8 characters";
}
if (user.Password.Length > 16)
{
this.PasswordValidationErrorTextBlock.Text = "Password can not exceed 16 characters.";
}
if (string.IsNullOrEmpty(user.Email))
{
this.EmailValidationErrorTextBlock.Text = "Email can not be left blank.";
}
}
This makes me cry a little inside each time I see it. There should never be any validation in your Code-Behind. Ever. Why you ask? Let's break it down.
- Testability. With this in the View's code-behind, you can't test any of your business rules.
- Reusability. We are writing the Create User View. What about Logging back in with an existing user? You'll want to re-use the same model wouldn't you? Ideally have part of the same validation, such as the blank email address being checked? You'll have to re-write the validation in each View with this approach.
- Violates the MVVM pattern. You should have all of the business logic outside of the View. The code-behind can be used to handle View related things, but never business logic.
View Model
Another option is to put the validation in the View Model, and let the UI know what the errors are by binding to error properties. In order to show this, we need to first remove the code-behind and create our View Model. It would look like this.
public class AccountCreationViewModel : INotifyPropertyChanged, ICommand
{
private string emailEmptyError;
private string passwordToShortError;
private string passwordToLongError;
private User appUser;
public event PropertyChangedEventHandler PropertyChanged;
public User AppUser
{
get { return appUser; }
set
{
appUser = value;
this.OnPropertyChanged("AppUser");
}
}
public string EmailEmptyError
{
get { return emailEmptyError; }
set
{
emailEmptyError = value;
this.OnPropertyChanged("EmailEmptyError");
}
}
public string PasswordToShortError
{
get { return passwordToShortError; }
set
{
passwordToShortError = value;
this.OnPropertyChanged("PasswordToShortError");
}
}
public string PasswordToLongError
{
get { return passwordToLongError; }
set
{
passwordToLongError = value;
this.OnPropertyChanged("PasswordToLongError");
}
}
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public void Execute(object parameter)
{
if (AppUser.Password.Length < 8)
{
this.PasswordToShortError = "Password must be a minimum of 8 characters.";
return;
}
if (AppUser.Password.Length > 16)
{
this.PasswordToLongError = "Password can not exceed 16 characters.";
return;
}
if (string.IsNullOrEmpty(AppUser.Email))
{
this.EmailEmptyError = "Email can not be left blank.";
return;
}
// Create the user
// ......
}
}
Already this is looking a bit hard to read. We are creating unnecessary properties in the View Model to keep track of validation errors. We would need to update our View to use the View Model as its data context along with updating the bindings on the TextBox's to bind to the ViewModel.AppUser object. We also need to bind our error TextBlocks to the error messages in the View Model
< Page.DataContext>
< viewModels:AccountCreationViewModel />
< /Page.DataContext>
< TextBox x:Name="EmailTextBox"
Margin="0 5 0 0"
MinWidth="200"
Text="{Binding Path=AppUser.Email,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />
< TextBox Margin="0 5 0 0"
MinWidth="200"
Text="{Binding Path=AppUser.Password,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />
< TextBlock Text="{Binding Path=PasswordToLongError}"
Foreground="Red"/>
< TextBlock Text="{Binding Path={PasswordToShortError}"
Foreground="Red"/>
< TextBlock Text="{Binding Path=EmailEmptyError}"
Foreground="Red"/>
Lastly, update our button so it fires the Excute method associated with the ICommand interface.
< Button Content="Create Account"
Margin="0 10 0 0"
Command="{Binding}" />
When the button is clicked, it will execute the command. The command will run through the business rules and update the error properties. Each time the error property is updated, the UI will reflect the errors.
This is still a bad approach to take. While this does make your code testable, your validation is still tightly coupled with your View (since each View should have their own View Model). It also continues to prevent your business logic from being reusable.
Custom validation in the Model
Now we get to the meat of this post. The validation should be placed in your Model object, so that your model is a complete object. It contains the properties it needs to do its job, it becomes self-validating and can be re-used through the app.
Moving our three error properties from the View Model in to the model is a bad idea however. The error properties are really used to assist the View in displaying errors and don't help the Model in any way. The model is already aware that it has validation issues. In WPF and WinRT for Windows 8 development, you would use INotifyDataErrorInfo. The View would be notified when a property validation error existed and could react accordingly. Unfortunately, we do not have that when you are building a Windows 8/Windows Phone Universal app. So I set out to create my own framework.
The first thing I did was created an interface that all models needing validation would be required to implement. I'll show the interface, then we will discuss it a bit further.
public interface IValidatable
{
Dictionary< string, List< IValidationMessage>> ValidationMessages { get; }
void AddValidationMessage(IValidationMessage message, string property = "");
void RemoveValidationMessage(string message, string property = "");
void RemoveValidationMessages(string property = "");
bool HasValidationMessageType< T>(string property = "");
IValidationMessage ValidateProperty(
Func< string,
IValidationMessage> validationDelegate,
string failureMessage,
string propertyName = "");
}
So let's go over each method on by one.
ValidationMessages
This is a read-only property, that will contain all of our validation messages. The property has a Key typed to a string, which will be the Models property name. The value is a collection of IValidationMessage
objects (We will discuss what the IValidationMessage is later). The idea being that for each property in the model, we can store more than 1 error.
AddValidationMessage(IValidationMessage message, string property = "");
This method is used to add a validation message to the ValidationMessages
collection. The property will be assigned as the Key, with the message being added as the value.
RemoveValidationMesssage(IvalidationMessage message, string property = "");
Just like we can add a validation message, we will provide ourselves with the ability to remove it.
RemoveValidationMessages(string property = "");
We can use this method to completely clear out all validation messages in one shot for a single property.
HasValidationMessageType< T>(string property = "");
This method will return true if the object has validation messages matching < T> and false if it does not.
ValidateProperty(
Func< string,
IValidationMessage> validationDelegate,
string failureMessage,
string propertyName = "");
This method can be called to actually perform validation on a property within the object and build the collection of errors. The arguments require a method delegate that returns an IValidationMessage object. This is how the validation becomes reusable. Each individual object can pass in a method delegate that performs the actual validation. The IValidatable implementation will take the results and determine if it must go in to the ValidationMessages collection or not.
IValidationMessage
It looks like a lot of the interface relies on a secondary interface called IValidationMessage doesn't it? This interface is really straight forward. It contains a single property called Message.
public interface IValidationMessage
{
string Message { get; }
}
The idea with this, is that we can create objects that implement this interface, but containing different types of messages. For instance, in this post, we will create a ValidationErrorMessage
and a ValidationWarningMessage
. You could go on and create any kind of messaging you want and use it for binding to the View.
Implementing the IValidatable
Rather than having our User
model implement this interface, we will implement it within a new Base class. We will call the class ValidatableBase
.
public abstract class ValidatableBase : IValidatable, INotifyPropertyChanged
{
private Dictionary< string, List< IValidationMessage>> validationMessages =
new Dictionary< string, List< IValidationMessage>>();
public event PropertyChangedEventHandler PropertyChanged;
}
Our initial class contains the Dictionary that will hold our validation messages. Next, we implement the read-only property required by our interface.
public Dictionary< string, List< IValidationMessage>> ValidationMessages
{
get
{
return this.validationMessages;
}
private set
{
this.validationMessages = value;
this.OnPropertyChanged("ValidationMessages");
}
}
The call to OnPropertyChanged
will let the UI know that this collection has changed. This in most cases won't be used since the collection is read-only, but since it is going in to a base class, we want to provide support for that.
Next, we implement our HasValidationMessageType
method.
public bool HasValidationMessageType< T>(string property = "")
{
if (string.IsNullOrEmpty(property))
{
bool result = this.validationMessages.Values.Any(collection =>
collection.Any(msg => msg is T));
return result;
}
return this.validationMessages.ContainsKey(property);
}
In this method, we check if the collection contains a Key matching the property supplied. If it does, then we check it's values to see if any of them match the Type specified in < T>. This lets you do something like
HasValidationMessageType< ValidationErrorMessage>("Email");
to check if the model has a validation error on the email property.
The AddValidationMessage
method is pretty easy as well.
public void AddValidationMessage(IValidationMessage message, string property = "")
{
if (string.IsNullOrEmpty(property))
{
return;
}
// If the key does not exist, then we create one.
if (!this.validationMessages.ContainsKey(property))
{
this.validationMessages[property] = new List< IValidationMessage>();
}
if (this.validationMessages[property].Any(msg => msg.Message.Equals(message.Message) || msg == message))
{
return;
}
this.validationMessages[property].Add(message);
}
In this method we create a new collection if the key doesn't exist yet, we then double check to ensure this validation message does not already exist in the collection. If not, we add it.
The two Removal methods are easy enough as well.
public void RemoveValidationMessage(string message, string property = "")
{
if (string.IsNullOrEmpty(property))
{
return;
}
if (!this.validationMessages.ContainsKey(property))
{
return;
}
if (this.validationMessages[property].Any(msg => msg.Message.Equals(message)))
{
// Remove the error from the key's collection.
this.validationMessages[property].Remove(
this.validationMessages[property].FirstOrDefault(msg => msg.Message.Equals(message)));
}
}
Here we just check if there is any message for the supplied Key and remove it. At the moment, this does not do any Type checking to see if there is more than one Type of object (Warning and Error) in the collection with the same message. The method just removes the first thing it finds and calls it good.
Removing the entire property from the collection is even easier.
public void RemoveValidationMessages(string property = "")
{
if (string.IsNullOrEmpty(property))
{
return;
}
if (!this.validationMessages.ContainsKey(property))
{
return;
}
this.validationMessages[property].Clear();
this.validationMessages.Remove(property);
}
We just check if a key exists that matches the property name and then clear out its messages contents and remove the key from the Dictionary.
Finally, we finish implementing the interface by building the ValidateProperty
method. In this method, we just invoke the delegate we are provided, and accept a IValidationMessage object in return. If the return value is not null, then we add it to the ValidationMessages
collection. If it is null, then we can assume that the validation passed and there are no issues. Since that is the case, we remove it from the validation collection.
public IValidationMessage ValidateProperty(
Func< string,
IValidationMessage> validationDelegate,
string failureMessage,
string propertyName = "")
{
IValidationMessage result = validationDelegate(failureMessage);
if (result != null)
{
this.AddValidationMessage(result, propertyName);
}
else
{
this.RemoveValidationMessage(failureMessage, propertyName);
}
return result;
}
We have satisfied the requirements of the IValidatable interface, but there is one more method we need to add to the base class. This will let us group all of our property validations in to a single call.
public abstract void Validate();
We mark it as abstract, since the base class has nothing to validate, and we want to force any object that inherits from the base class to implement the method. If you don't want to do this, you can opt out of in your code. Not everyone needs to have this feature, thus the reason why it was left out of the interface.
Also, our base class implements the INotifyPropertyChanged method, so we will remove it from our model and put the implementation in to our base class.
public void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Revisiting our Model
Finally, we can revisit the model and add our fancy new validation. The first thing we have to do is change our class declaration. Initially, we implemented the INotifyPropertyChange interface, but since our base class implements this, we don't need to now. We just need to inherit from our base class.
public class User : ValidatableBase
Now that we are inheriting from our base class, we need to implement the required Validate()
method. In order to keep with the Single-Responsibility-Principle, we will invoke other methods from within the Validate() method. Since we have to validate multiple properties, we should have each property validation be contained within it's own method. This makes it easier to test.
public override void Validate()
{
this.ValidatePassword("Password");
this.ValidateEmail("Email");
// Passing in an empty string will cause the ValidatableBase indexer to be hit.
// This will let the UI refresh it's error bindings.
base.OnPropertyChanged(string.Empty);
}
Here we just invoke a ValidatePassword
and ValidateEmail
method. When we are done, we notify any observers that the entire object has changed by not specifying a property name in the call to OnPropertyChanged. This lets the observers (in this case, the View) know its bindings need to be refreshed.
public IValidationMessage ValidatePassword(string property)
{
const string passwordToShortError = "Password must a minimum of 8 characters in length.";
const string passwordToLongError = "Password must not exceed 16 characters in length.";
if (this.Password.Length < 8)
{
var msg = new ValidationErrorMessage(passwordToShortError);
return msg;
}
if (this.Password.Length > 16)
{
var msg = new ValidationErrorMessage(passwordToLongError);
return msg;
}
return null;
}
public IValidationMessage ValidateEmail(string property)
{
const string emailAddressEmptyError = "Email address can not be blank.";
if (string.IsNullOrEmpty(this.Email))
{
var msg = new ValidationErrorMessage(emailAddressEmptyError);
return msg;
}
}
Now our model has proper validation. It is testable and reusable. Much, much better. Next, we need to revise our View Model. We will delete all of the error properties within it, along with the INotifyPropertyChanged implementation. We will only need the AppUser
property and the ICommand implementation.
public class LoginPageViewModel : ICommand
{
public LoginpageViewModel()
{
this.AppUser = new User();
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
this.AppUser.Validate();
if (this.AppUser.HasValidationMessageType< ValidationErrorMessage>())
{
return;
}
// Create the user.
}
}
We now have one more thing to do. We need to update our XAML. The Error TextBlocks will now bind to the ValidationMessages property within the model, using an index matching the property they are bound to.
<TextBlock data-preserve-html-node="true" data-preserve-html-node="true" Text="{Binding Path=AppUser.ValidationMessages[Password],
Converter={StaticResource ValidationMessageConverter}}"
Foreground="Red"/>
<TextBlock data-preserve-html-node="true" data-preserve-html-node="true" x:Name="EmailValidationErrorTextBlock"
Text="{Binding Path=AppUser.ValidationMessages[Email],
Converter={StaticResource ValidationMessageConverter}}"
Foreground="Red" />
You can see in this XAML that I am using a value converter when binding to the collection. That is because the binding is to an implementation of IValidationMessage. The UI does not know how to render this, so rather than overriding the ToString() method on each IValidationMessage implementation, I wrote a Value Converter.
public class ValidationMessageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (!(value is IEnumerable< IValidationMessage>))
{
return string.Empty;
}
var collection = value as IEnumerable< IValidationMessage>;
if (!collection.Any())
{
return string.Empty;
}
return collection.FirstOrDefault().Message;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
Now, when you run the app and enter an invalid Email or Password, the UI will automatically inform you of the validation errors when you press the Create Account button. If you ever need to add more Email validation (such as the proper email format) or more Password validation (such as not allowing specific characters) you can do so without needing to modify your View Model or your View.
If you need to add a whole new property to the Model, with validation, you can. You don't need to modify your View Model, you only need to add a TextBlock to the View to display the validation.
Before we end the post, I will show you two implementations of the IValidationMessage. They both do the same thing, but are Typed differently so that you can segregate your messages by Type. This gives more flexibility that using an Enum.
First is the Error validation message.
public class ValidationErrorMessage : IValidationMessage
{
public ValidationErrorMessage() : this(string.Empty)
{ }
public ValidationErrorMessage(string message)
{
this.Message = message;
}
public string Message { get; private set; }
}
Next is the Warning validation message.
public class ValidationWarningMessage : IValidationMessage
{
public ValidationWarningMessage() : this(string.Empty)
{ }
public ValidationWarningMessage(string message)
{
this.Message = message;
}
public string Message { get; private set; }
}
You can now potentially create a ItemsControl in your XAML, bind it to the ValidationMessages
collection and build a DataTemplate
for each IValidationMessage implementation, each with different styles applied. So you could have orange TextBlocks for Warnings and Red for Errors.
I hope you guys find this useful while building Windows 8/Windows Phone 8 Universal apps!