Almost a year ago, I posted a lengthy article on how to implement Async Validation with RIA Services and DataForm. It was hard; much harder than it should have been, and we all knew it. Since then, things have changed, and async validation is a lot simpler. Let’s take a look at why it’s easier and how you can easily perform async validation in your application.
INotifyDataErrorInfo
Silverlight 4 shipped with an interface called INotifyDataErrorInfo, and controls that previously supported validation by way of exceptions now support async validation through this interface. This laid the groundwork for RIA Services to offer a simple mechanism for adding validation errors to entities at arbitrary times and having the controls show those validation errors just like all others.
INotifyDataErrorInfo includes an ErrorsChanged event that is used to notify controls that errors have changed for the object. After this event is raised, the control (for instance the TextBox) will call the GetErrors method to see what errors there are for the property or object that had its errors change. For entity-level errors, a null or empty property name is used.
Implementing this interface can actually be a bit of work. You need to maintain a list of errors for all properties as well as the entity itself, and as any changes to the errors are made, you need to raise the appropriate events. It’s not rocket science to implement the interface; it’s just a bit of plumbing, especially if you want to allow for errors to be changed in bulk and raise only the minimal number of events to cover the spectrum.
Entity.ValidationErrors
The RIA Services Entity class previously had a read-only list of its current validation errors. When we implemented INotifyDataErrorInfo, we took this a step further though, and we made the property an ICollection<ValidationResult>, allowing you to call Add, Remove, and Clear on the collection to manipulate the errors at any time, even outside of the standard DataAnnotations validation stack. If you want to roll your own validation engine, you can do so and simply pump ValidationResult instances into the ValidationErrors collection.
The best part of this collection is that it’s a custom collection class that I created that sends notifications back to the Entity when errors have changed overall, for a property, or at the entity level. This allows the entity to then easily raise condensed ErrorsChanged events using the INotifyDataErrorInfo interface. This means that when you call Add, Remove, or Clear on the ValidationErrors collection, we translate that into the necessary INotifyDataErrorInfo events so you don’t have to--simply modifying the collection will result in the UI showing the updated errors; it can’t get any simpler.
Async Validation Example
Validating the availability of a username is a canonical asynchronous validation scenario. We can implement this scenario by starting with a Silverlight Business Application solution. Create one of these projects from the Silverlight tab in the New Project wizard. Once you have the blank project, we can implement this async validation in only a few steps.
First, we need to create a DomainService method that can be called to determine if a username is available. We’ll do this in the UserRegistrationService.cs file that is created for you in the Services folder. Within the UserRegistrationService class, add the following method:
[Invoke]
public bool IsUsernameAvailable(string username)
{
return !Membership.FindUsersByName(username).Cast<MembershipUser>().Any();
}
Second, we need to create a custom validator that can invoke this DomainService method on the server synchronously, or use the generated DomainContext from the client to make the call asynchronously. Create a new class file in the server project and call it UsernameValidator.shared.cs. By using the .shared.cs extension, the code will automatically be available to the Silverlight project as well as the server project. Here’s the code we need in the class; notice that some of the code runs on the server, and some runs from Silverlight, using #if directives.
using System.ComponentModel.DataAnnotations;
#if SILVERLIGHT
using System.ServiceModel.DomainServices.Client;
#endif
namespace AsyncUsernameChecker.Web
{
public static class UsernameValidator
{
public static ValidationResult IsUsernameAvailable(string username,
ValidationContext validationContext)
{
ValidationResult errorResult = new ValidationResult(
"Username already taken",
new string[] { validationContext.MemberName });
#if !SILVERLIGHT
UserRegistrationService service = new UserRegistrationService();
if (!service.IsUsernameAvailable(username))
{
return errorResult;
}
#else
UserRegistrationContext context = new UserRegistrationContext();
InvokeOperation<bool> availability = context.IsUsernameAvailable(username);
availability.Completed += (s, e) =>
{
if (!availability.HasError && !availability.Value)
{
Entity entity = (Entity)validationContext.ObjectInstance;
entity.ValidationErrors.Add(errorResult);
}
};
#endif
return ValidationResult.Success;
}
}
}
On the server, all validation must occur synchronously, and we do that by invoking the UserRegistrationService.IsUsernameAvailable method directly; if the username is not available, we return an error result, otherwise we return ValidationResult.Success.
On the client, this validation will need to be performed asynchronously. The approach I took here was to to return ValidationResult.Success from the validation after invoking a call to the UserRegistrationContext.IsUsernameAvailable method. When that invocation completes (asynchronously), we check the return value of the method (and also that no errors occurred). If the method returned false, then we add the error result to the entity’s ValidationErrors collection. As stated above, by simply adding a validation result to the error collection, the appropriate INotifyDataErrorInfo event will be raised, and the UI will be updated with the error message.
Third, and last, we need to apply a [CustomValidation] attribute to our property that needs to utilize this validation. Open up the Models\RegistrationData.cs file and find the UserName property; it already has a bunch of metadata applied to it. We need to add another attribute, such that the property is now represented as follows:
/// <summary>
/// Gets and sets the user name.
/// </summary>
[Key]
[Required(ErrorMessageResourceName = "ValidationErrorRequiredField",
ErrorMessageResourceType = typeof(ValidationErrorResources))]
[Display(Order = 0, Name = "UserNameLabel",
ResourceType = typeof(RegistrationDataResources))]
[RegularExpression("^[a-zA-Z0-9_]*$",
ErrorMessageResourceName = "ValidationErrorInvalidUserName",
ErrorMessageResourceType = typeof(ValidationErrorResources))]
[StringLength(255, MinimumLength = 4,
ErrorMessageResourceName = "ValidationErrorBadUserNameLength",
ErrorMessageResourceType = typeof(ValidationErrorResources))]
[CustomValidation(typeof(UsernameValidator), "IsUsernameAvailable")]
public string UserName { get; set; }
The line we added is [CustomValidation(typeof(UsernameValidator), “IsUsernameAvailable”)]. This indicates that the property uses a custom validation method defined on the UsernameValidator class, under the method name of IsUsernameAvailable. It’s the method we created above, and we made it available for both the server and the client.
We’re done. Three steps and we now have fully-functioning async validation. Run your app, register a user, log out, and then try to register the same username again and you’ll see the validation error pop up.
Validation Failures from the Server
I really favor the approach of immediately returning Success for async validation, and then only adding the error if the validation came back as a failure. This allows the end user to continue with other data entry but get informed if something they entered was found to be invalid. If the async validation cannot be processed for any reason, and the user submits the data to the server, the validation will be invoked again on the server and the error message will be returned all the way back to the client and the UI will display the error message. This is all possible because of how we process validation failures from the server.
When validation errors occur on the server, we serialize those messages and send them back to the client, aborting the data submission that was requested. When the client receives these error messages, new ValidationResult instances are created and added to the entity’s ValidationErrors collection. Because of INotifyDataErrorInfo support, this can happen asynchronously and the ErrorsChanged event will be raised, notifying the UI and displaying the error messages. In the end, the experience should be seamless for both the application developer and (more importantly) the end user. Ultimately, there should be no way to distinguish client-side validation errors from server-side validation errors--no matter which tier the validation errors occurred on, RIA Services does the work for you of informing the UI and getting the message displayed.
Limitations
There are some limitations to the async validation support that you should be aware of though; the limitations are really in the Silverlight SDK/Toolkit controls. With DataGrid or DataForm, you can only have a single entity in edit mode at any time; while that entity is being edited, the UI will respond to INotifyDataErrorInfo events and display error messages appropriately. But if the user has made changes to multiple entities, or has simply navigated away from an entity that later is determined to have an error, the controls won’t do anything to change currency or navigate back to an entity that has an error. This limitation really boils down to the fact that there’s no single way to address this problem for all applications--you need to handle this yourself, however you see fit for your application. When you call SubmitChanges for a DomainContext, be sure to handle its error condition and check for validation errors on entities, then make your application handle the conditions of errors on entities that aren’t currently active in the UI.
Emerging Patterns?
I’d love to hear about patterns that you see emerging in your code for how you are implementing asynchronous validation. If you tackle the above limitation in a clever way, do share. And if you find other good ways to write async validation methods, please share your approaches. We’d love to hear how we can further improve the experience for validation.
Also, Nikhil Kothari (the RIA Services architect) took a slightly different approach when he was implementing async validation. Instead of plugging it in with the validation attribute, we added explicit calls to a validation method from both his DomainService and his client (in the UI). He has a great post that goes pretty deep with several aspects of validation.