This article is compatible with the latest version of Silverlight.
This article is Part 2 of the series “The validation story in Silverlight”.
In the previous part of this series, we looked at the basic validation options we have available Silverlight (since version 3). The problem with the solutions we looked at is that they are mostly based on client-side validations. As you saw, the validation code, such as the attributes, assumes that all our validation needs are fulfilled with these attributes. In many cases, we’ll need to go to the server for a specific business rule.
For example, we need to check if a user name already exists. We can’t solve that on the client, because for this, we’ll need to perform a call to the database over a service. When the service returns, it’s possible that the validation has failed. The object enters a non-valid state and we should be able to notify our UI about that. If you’re like me, you don’t want to write all that code yourself, so we have to put our hopes in the Silverlight framework to handle this in some way.
The good news is that Silverlight 4 added support for this and more. More specifically, support was added for the IDataErrorInfo and the INotifyDataErrorInfo interfaces. We’ll start by looking at these in this article. Furthermore, we’ll take a look as well at how we can change the default looks of the validation: it’s very likely your application will use some other way of notifying the user that the value of a field is not correct.
We use the same solution as with part 1, however, some more code was added. The code can be downloaded here.
Hello IDataErrorInfo
First things first: let’s take a look at the definition of the interface.
public interface IDataErrorInfo
{
string Error { get; }
string this[string columnName] { get; }
}
The Error property can be used to return error information about the object itself. In some cases, this is not used however and can be set to Null. Note that in data binding, this property is never called, so it’s only useful in custom error reporting.
public string Error
{
get
{
return null;
}
}
More important is the indexer. It can be used to write business rules for the different properties of the object. The columnName parameter refers to the name of the property and based on that name, we can trigger validation logic. The following snippet shows how we can use this indexer.
public string this[string columnName]
{
get
{
switch (columnName)
{
case "FirstName":
if (string.IsNullOrEmpty(FirstName))
return "First name can't be empty";
else if (FirstName.Length > 50)
return "First name should be less than 50 characters";
break;
case "LastName":
if (string.IsNullOrEmpty(LastName))
return "Last name can't be empty";
else if (LastName.Length > 50)
return "Last name should be less than 50 characters";
break;
case "Age":
if (Age < 0)
return "Age can't be negative";
else if (Age > 120)
return "You are too old";
break;
default:
break;
}
return null;
}
}
Using the switch block, we check which property value needs to be validated. While in this sample, all logic ends up in this one location, it’s very well possible to extract this into a separate validation object, perhaps even containing some reusable (read: standard methods such as checking for string length).
Now when data binding to an object that implements this interface, the same type of error messages are created for us in the UI. We do need to make one very important change: set the ValidateOnDataErrors property to true. This is a signal for the data binding engine that the object we are binding to implements the interface.
<TextBox Name="FirstNameTextBox" Text="{Binding FirstName, Mode=TwoWay, ValidatesOnDataErrors=True, NotifyOnValidationError=True}"
/>
If needed, we can apply both ValidatesOnExceptions and ValidatesOnDataErrors at the same time. However, if an exception occurs, the ValidatesOnDataErrors will never get called.
While this is a clean way of performing validation, it still is aimed at simple, client-side only validation, that doesn’t solve the problem outlined earlier: performing a validation that involves asynchronous validations on the server. The interface has some other problems as well. For example, it’s not possible to return more than one exception per property (while this can be the case in real world objects). The interface does not support returning anything but a string (so no custom Exception types).
To solve this, the INotifyDataErrorInfo comes in handy.
The INotifyDataErrorInfo interface
Next to the IDataErrorInfo, with Silverlight 4 came the INotifyPropertyChanged. Let’s start by looking at the interface definition.
public interface INotifyDataErrorInfo
{
bool HasErrors { get; }
event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
IEnumerable GetErrors(string propertyName);
}
public sealed class DataErrorsChangedEventArgs : EventArgs
{
public DataErrorsChangedEventArgs(string propertyName);
public string PropertyName { get; }
}
Two things should pop out. First, notice the GetErrors, returning an IEnumberable. This makes it possible to have more than one exception on a single property. Next, the ErrorsChanged event can be used where we need to notify that the errors on the object have somehow changed (that is, after the return of an async call). Because of this, it’s easy to see that this interface is a perfect fit for Silverlight!
Below is the new Person class (relevant part) with this interface applied.
public class NotifyDataErrorInfoPerson: INotifyDataErrorInfo, INotifyPropertyChanged
{
private string _firstName;
private string _lastName;
private int _age;
private Dictionary<string, List<CustomValidationException>> _errors;
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public event PropertyChangedEventHandler PropertyChanged;
...
private void NotifyErrorsChanged(string propertyName)
{
if (ErrorsChanged != null)
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
Dictionary<string, List<CustomValidationException>> Errors
{
get
{
if (_errors == null)
{
_errors = new Dictionary<string, List<CustomValidationException>>();
}
return _errors;
}
}
bool INotifyDataErrorInfo.HasErrors
{
get
{
return (Errors.Where(error => error.Value.Count() > 0).Count() > 0);
}
}
IEnumerable INotifyDataErrorInfo.GetErrors(string propertyName)
{
if (!Errors.ContainsKey(propertyName))
{
Errors.Add(propertyName, new List<CustomValidationException>());
}
return Errors[propertyName];
}
}
public class CustomValidationException
{
public int ErrorLevelId { get; set; }
public string Message { get; set; }
}
Several things are important in this class now. First, notice we have included a Dictionary<string, List<CustomValidationException>>. The CustomValidationException is a custom class, containing some more info that just a string. The Dictionary itself is added to keep track of all errors on the properties. Indeed, for each property, we create a new KeyValuePair where the Key is the property name and the Value is a List<CustomValidationException>, making it possible to have more than one error per property. Next, notice the use of the ErrorsChanged event. We will raise this event whenever there’s a change in the errors on any property.
Let’s now use this async validation. A new property was added, UserName. Usernames need to be unique, but we can’t check that on the client (sending down all already taken usernames would be silly): we need to make a call to a service. In this case, the service is performing a hardcoded check for the username:
[OperationContract]
public bool CheckUserNameUnique(string userName)
{
if (userName == "gillcleeren")
{
Thread.Sleep(3000);
return false;
}
return true;
}
Note that the Thread.Sleep(3000) was added to better see the async behavior.
In the setter of the UserName, we now call this service.
public string UserName
{
get
{
return _userName;
}
set
{
_userName = value;
NotifyPropertyChanged("UserName");
CheckUserNameUnique();
}
}
private void CheckUserNameUnique()
{
UserValidatorService.UserValidatorServiceClient proxy =
new UserValidatorService.UserValidatorServiceClient();
proxy.CheckUserNameUniqueCompleted +=
new EventHandler<UserValidatorService.CheckUserNameUniqueCompletedEventArgs>
(proxy_CheckUserNameUniqueCompleted);
proxy.CheckUserNameUniqueAsync(UserName);
}
Nothing special here, this service call will be triggered whenever a new value is entered into the field. In the callback, we handle the response of the server: we check if the response is false. If so, we create a new CustomValidationException instance that we add in the List<CustomValidationException> for the key “UserName” in the errors dictionary. If the response is true, the error should be removed from the List again.
void proxy_CheckUserNameUniqueCompleted(object sender, UserValidatorService.CheckUserNameUniqueCompletedEventArgs e)
{
bool result = e.Result;
CustomValidationException exc;
if (e.Error != null || !e.Result)
{
if (result == false)//we have a failing unique constraint
{
exc = new CustomValidationException();
exc.ErrorId = FAILING_UNIQUE_ERROR_ID;
exc.ErrorLevelId = 100;
exc.Message = "This username not unique";
//add error for the property in the dictionary
CreatePropertyErrorList("UserName");
//there is no error in the collection with the specified ID
if (_errors["UserName"].SingleOrDefault(c => c.ErrorId == exc.ErrorId) == null)
{
_errors["UserName"].Add(exc);
}
NotifyErrorsChanged("UserName");
}
}
else if (e.Result)
{
CreatePropertyErrorList("UserName");
exc = _errors["UserName"].SingleOrDefault(c => c.ErrorId == FAILING_UNIQUE_ERROR_ID);
if (exc != null)
{
_errors["UserName"].Remove(exc);
}
NotifyErrorsChanged("UserName");
}
}
Whenever something changes in the collection of errors (here it happens asynchronously, and you’ll see this very well because of the Thread.Sleep() we added earlier), we call the NotifyErrorsChanged method, which in turn calls the ErrorsChanged event.
private void NotifyErrorsChanged(string propertyName)
{
if (ErrorsChanged != null)
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
In the binding expression, we need to tell Silverlight it has to watch for the ErrorsChanged event being thrown from the bound source object by specifying the ValidatesOnNotifyDataErrors=true.
<TextBox Name="UserNameTextBox" Text="{Binding UserName, Mode=TwoWay, ValidatesOnNotifyDataErrors=True, NotifyOnValidationError=True}"
/>
The result is, of course, similar:
Customizing the error display
Up until here, we have always used the default way of displaying errors on fields: the red popup containing the error message. It’s very well possible that this default is not a good fit for your application, so we need to change it. The good news is that this validation popup is part of the control template: several controls have a VisualStateGroup called VadiationStates that dictates how the control will look in Valid, InvalidFocused and InvalidUnfocused. InvalidFocused by default shows the red Border, the red triangle in the top right corner and the red popup. InvalidUnfocused hides the popup.
Let’s change this default look. Start by opening the project in Expression Blend 4. Right-click on the UserNameTextBox and select Edit Template --> Edit a copy. Give it a name, for example CustomValidationTemplate. Blend will now enter the template editing mode.
Take a look at the Objects and Timeline window. As the last element in the list, you can see the ValidationErrorElement, the red Border, containing a few other elements, such as the small red triangle appearing when an error is displayed. In the States window, note the ValidationStates stategroup with the above mentioned states.
Since I’m no designer, my custom template may look at bit basic: we will at a red * when the field goes into the invalid state. To do so, make sure the Base state is selected and add a TextBlock in the template, containing the *. In this Base state, set its Opacity to 0.
Now for both the InvalidFocused and InvalidUnfocused, set the Opacity of the TextBlock to 100 and (for demo purposes) set the Opacity of the ValidationErrorElement to 0.
Now run the application again and we can see the custom validation template in action.
Summary
This concludes the overview of validation options currently available in Silverlight 4. The most fundamental option is of course the INotifyDataErrorInfo, which is due to its support for async validation a perfect fit for Silverlight. Validation in Silverlight is ready for real world scenarios!
About the author
Gill Cleeren is Microsoft Regional Director (www.theregion.com ), MVP ASP.NET, INETA speaker bureau member and Silverlight Insider. He lives in Belgium where he works as .NET architect at Ordina. Passionate about .NET, he’s always playing with the newest bits. In his role as Regional Director, Gill has given many sessions, webcasts and trainings on new as well as existing technologies, such as Silverlight, ASP.NET and WPF. He also leads Visug (www.visug.be), the largest .NET user group in Belgium. He’s also the author of “Silverlight 4 Data and Services Cookbook”, published by Packt Publishing. You can find his blog at www.snowball.be .