Watch recordings of Brian's WCF RIA Services webinars:
This article is Part 6 of the series WCF RIA Services.
Introduction
In Part 5 I discussed metadata classes and shared code, and one of the example uses of metadata classes I showed was using a [Required] validation attribute to cause some validation on both the client and server side. In this article, I will dive into more depth on the various ways you can validate data using WCF RIA Services. I’m going to look at four ways of validating input data:
- Using built-in validation attributes from the System.ComponentModel.DataAnnotations namespace
- Using custom validation attributes to invoke custom logic on the server and client side for cross property validation
- Using server side validation when changes are submitted to the back end
- Doing asynchronous validation using a domain service invoke operation
To demonstrate these things in the application context that I have been building up over the last 5 articles, I needed to add some more functionality. That functionality included some small schema changes to the database, so a new database creation script is included with the download code to create an appropriate database schema with some minimal sample data to be able to run the application. Because I am continuing to keep the focus on WCF RIA Services capabilities and not on general Silverlight display functionality, the display of some of the validation errors in the sample is necessarily crude to keep the changes to the app to a minimum and to keep the article length digestible.
The new functionality includes the ability to add customers (and there are a couple defined in the DB script so you don’t have to add any to get started), as well as select a customer associated with a given Task. There are validations associated with the definition of a Customer, as well as for creating time entries associated with a task that has a customer, described in more detail later in the article.
The finished code for this article can be downloaded here.
Data Annotation Validation Attributes
As mentioned last time, WCF RIA Services has rich built-in support for the validation attributes in the System.ComponentModel.DataAnnotation namespace. These attributes include the ability to specify simple forms of validation on entity properties by adding an attribute to an entity property. If your entity model is one where you cannot modify the properties directly (such as the code generated entities of Entity Framework), you can place the validation attributes on the properties of your metadata class. Those validation attributes will be used for server side validation by WCF RIA Services, and they will also be used client side because the client generated entities will have those attributes on their properties.
The available validation attributes include:
- Required
- StringLength (added by Entity Framework for string fields and therefore generated on the client)
- Regular Expressions
- Range (for numeric inputs)
There are a few others in the namespace that you might want to look into that purely affect display, but I won’t go into details of those in this article.
To apply them when using an entity framework model, you again just add them to the properties of your metadata class.
The generated client code will include those defined in your metadata class, as well as any defined on the model entities themselves, such as the [StringLength] attributes added by Entity Framework for string fields in the database.
As an example of using one of these attributes, a Customer has a Phone property that is a string, but needs to follow a proper format for a phone number. A regular expression is a great way of checking this kind of thing. So the metadata class for my Customer entity type includes the following attribute on the Phone property:
[MetadataTypeAttribute(typeof(Customer.CustomerMetadata))]
public partial class Customer
{
internal sealed class CustomerMetadata
{
...
[RegularExpression(@"^\D?(\d{3})\D?\D?(\d{3})\D?(\d{4})$", ErrorMessage="...")]
public string Phone { get; set; }
...
}
You can see the only challenge in using the regular expression validation is in coming up with the right regular expression. The one I am using here allows several formatting variants of US phone numbers (3 digits, 3 digits, 4 digits with variations on delimiters in between).
Just by having that attribute on the server entity or its metadata class, it shows up on the code generated client entity. Additionally, the WCF RIA Services Entity base class that is added to the client entity has an implementation of INotifyDataErrorInfo that uses the data annotation attributes to perform validation when data bound in the UI. So just by adding these attributes with appropriate error messages, you get validation indications for the user like shown below.
The error message shown on the Phone input TextBox happens automatically when the focus leaves the control. In the sample code, I used a behavior to cause the binding to update on each keystroke to get more immediate feedback to the user. The error message shown is the one specified in the server side attribute. You can also see that I added a ValidationSummary control to the form. That is to display when there is more than one validation error on the form at a time, which will come into play in the next section.
Custom Validation Attributes
In addition to the pre-built validation attributes discussed in the last section, there is a [CustomValidation] attribute in the data annotations namespace that allows you to point to a static method you can define to perform any kind of custom logic you need to validate when a property is set. That may involve doing some calculation or conditional logic, but ultimately you are deciding whether the newly input values are valid. Keep in mind that if you put these attributes on the server side entity or its metadata class, you will need to ensure that the code defined in the custom validation will be able to execute on both the client and server side. So you would not want to put code in there that does a look up against the database for example. I’ll show examples of how to deal with that kind of validation in the next two sections.
One place custom validations come in very handy is for cross-property validations. When the validity of one property’s value depends on the value of another property on the same object, or possibly the values of some of the child objects or a parent object, you can accommodate this fairly easily using custom validation.
In the Customer class for the sample, I added a contrived property called HasPhone. The simple idea here is that if HasPhone is set to true, then a valid phone number should be supplied. If HasPhone is set to false, no phone number should be supplied. This validation should be triggered whenever either property is modified, and both properties can be displayed as having a validation error when they are out of sync. You can see this in action below. Notice that both input controls (Phone TextBox and HasPhone CheckBox) are indicating a validation error, and there are two errors shown in the ValidationSummary control.
To support this, you first define the validation method that you want to point to from the [CustomValidation] attribute. That method has to have a particular signature, specifically it must be public, return a ValidationResult, and take in a value and a ValidationContext. If you are doing two properties with different types on the properties like I am here, you can break it down into two methods with each value having the appropriate type, even if they share some validation code:
public class CustomerPhoneValidator
{
public static ValidationResult CrossValidatePhoneWithHasPhone(string newPhone, ValidationContext context)
{
Customer customer = context.ObjectInstance as Customer;
if (customer == null) return ValidationResult.Success;
bool hasPhone = customer.HasPhone;
string phone = newPhone;
return ValidatePhoneAndHasPhone(hasPhone, phone);
}
public static ValidationResult CrossValidateHasPhoneWithPhone(bool newHasPhone, ValidationContext context)
{
Customer customer = context.ObjectInstance as Customer;
if (customer == null) return ValidationResult.Success;
bool hasPhone = newHasPhone;
string phone = customer.Phone;
return ValidatePhoneAndHasPhone(hasPhone, phone);
}
private static ValidationResult ValidatePhoneAndHasPhone(bool hasPhone, string phone)
{
if (hasPhone && string.IsNullOrWhiteSpace(phone))
{
return new ValidationResult(@"If the customer has a phone, the number must be specified",
new string[] { "HasPhone", "Phone" });
}
else if (!hasPhone && !string.IsNullOrWhiteSpace(phone))
{
return new ValidationResult(@"If the customer does not have a phone, the number should not be specified",
new string[] { "HasPhone", "Phone" });
}
else
{
return ValidationResult.Success;
}
}
}
You can see that I have two validation methods, one for the boolean HasPhone property and one for the string Phone property. Other than the type of the value, the signature of the methods is the same. You can see that the ValidationContext argument that is passed to your method allows you to get a reference to the whole object that the property being validated is on, allowing you to get to other properties on that object. At the point where your validation method is being called, the target property (i.e. Phone) has not been set on the object itself yet. The value being passed in to your validation method is the proposed new value, and it is up to your method to decide whether to allow it to be set or not. If you are fine with the value being set, you just return a ValidationResult.Success value. If you do not like the value, then you can return a ValidationResult with an error string populated and optionally a string array containing all of the property names affected by the error. This is how you can address cross-property validation scenarios like the one here. If no phone or an invalid phone number is supplied, I can check to see if the HasPhone property is set to true. If so, both Phone and HasPhone are in error because they have to agree with respect to their supplied values.
To apply the custom validation to a property, you just use a [CustomValidation] attribute:
[MetadataTypeAttribute(typeof(Customer.CustomerMetadata))]
public partial class Customer
{
internal sealed class CustomerMetadata
{
...
[CustomValidation(typeof(CustomerPhoneValidator),"CrossValidatePhoneWithHasPhone")]
[RegularExpression(@"^\D?(\d{3})\D?\D?(\d{3})\D?(\d{4})$", ErrorMessage="...")]
public string Phone { get; set; }
[CustomValidation(typeof(CustomerPhoneValidator), "CrossValidateHasPhoneWithPhone")]
public bool HasPhone { get; set; }
...
}
}
In this case I have a separate attribute for the regular expression and the cross-property validation for demonstration purposes. That will result in two different errors at times. For a more user friendly experience, as long as you are writing custom validation logic for a property tied in with the [CustomValidation] attribute, you might want to move that format validation inside the custom validation logic so that you only return one error at a time from a single control value.
Server-Side Validation
Some kinds of validation are not appropriate to do on the client side. You may have data you need to look up on the back end that you don’t want to expose to the client that will drive the decision of whether a given chunk of data is valid or not. For the sample application, I added the ability to look up a customer status on a customer when a task is saved. If the customer status (which I don’t want exposed to the client so that it doesn’t get inadvertently displayed in the client) is marked as “DeadBeat” status, then I want to limit the number of billable hours I accrue for that customer. So at the point where I go to save a task on the back end, I need to look up the customer status and see how many hours have been added to the modified Task as child TimeEntries and decide whether to accept that task for update. If I don’t want to accept it, I can throw a ValidationException from the update method, and validation errors will be added to the entity in the client application and can be displayed in various ways.
To facilitate this scenario, it did require a change to my object model. I talked briefly about the [Composition] attribute in the last article. In order to have the TimeEntries sent down with the Task even if they have not changed themselves (such as when a customer has just been assigned to the Task), I needed to switch to having the TimeEntries collection on the Task be a [Composition] relationship. When you do that, the TimeEntries become wholly owned by their parent Task, and you don’t submit updates to individual time entries the way the application was doing in the last couple articles. Now you just get updates to tasks, and those changes might involve changes to the discrete properties of the task, the assignment of a related customer (which is not a Composition relationship because customers can be independently queried and updated) or the addition/removal/change of a time entry within the child TimeEntries collection of the Task. You can check out the UpdateTask method in the domain service to see the way this altered the coding patterns. Basically you have to do some state detection yourself and call the appropriate parts of the data access API (Entity Framework in this case) to manage the inserts/updates/deletes of your child objects in a composition relationship.
By going with a composition relationship, though, now all the child TimeEntries are sent down with a modified task and you can evaluate the package as a whole for server side validation. So the update and insert methods for Tasks call a helper method that first does the server side validation:
private void ValidateCustomerHours(Task task)
{
if (task.CustomerId.HasValue)
{
int customerId = task.CustomerId.Value;
bool timeAllowed = IsTotalTimeAllowed(customerId, task.TotalTime);
if (!timeAllowed)
throw new ValidationException(
"You cannot enter more than 10 hours per billing cycle for this customer.");
}
}
[Invoke]
public bool IsTotalTimeAllowed(int customerId, TimeSpan totalTime)
{
Customer relatedCustomer = ObjectContext.Customers.Where(c => c.CustomerId == customerId).FirstOrDefault();
if (relatedCustomer != null && relatedCustomer.Status == "Deadbeat"
&& (totalTime > TimeSpan.FromHours(10)))
{
return false;
}
return true;
}
If I wrote the client so that it did not validate the max hours for a customer on the client side before sending the task to the back end for updating or inserting, this code would be invoked and would throw the ValidationException back to the client. On the client side, WCF RIA Services would add a ValidationError to the Task and it could be displayed by the build in validation mechanisms of Silverlight Controls.
Async Validation through Invoke Methods
When it comes to validation, it is generally better to let the user know as early as possible when they have entered invalid data instead of waiting until a later point such as when they have tried to send that invalid data to the back end for persistence. A decent way to do this in WCF RIA Services is to use an [Invoke] method on your service. In addition to the CRUD methods you can expose on a WCF RIA Service, you can also add arbitrary methods that you mark with an [Invoke] attribute. These too will be exposed as service methods to the client and will show up on the corresponding domain context. They can pass different types than the main entities that the domain service is managing as entities, but there is a restricted set of types that you can pass as parameters and return types. The WCF RIA Services enumerates the types that can be used as parameters and return types on domain service methods.
To support more immediate feedback to the user for the case of entering too many hours for a customer with bad status, I added the IsTotalTimeAllowed method that you can see in the code snippet in the previous section. That method is being used both internally by the server side validation, and can be called directly from the client as well. In the client side view model, at the point where the user has entered a new time entry, I can make a call to that service method to check whether the new time entry will exceed the max allowed hours.
TimeEntry entry = popup.DataContext as TimeEntry;
...
if (SelectedTask.CustomerId.HasValue)
{
TasksDomainContext context = new TasksDomainContext();
Action<InvokeOperation<bool>> action = delegate(InvokeOperation<bool> op)
{
...
if (entry != null) SelectedTask.TimeEntries.Add(entry);
...
};
context.IsTotalTimeAllowed(SelectedTask.CustomerId.Value,
SelectedTask.TotalTime + (entry.EndTime - entry.StartTime), action, null);
}
As you can see, the invoke method shows up on the domain context as a normal async service method. You can pass a callback delegate of type
InvokeOperation<T>, where T is the return type of the invoke method, and that callback will be invoked when the async call is complete. You get the same thread marshalling back to the UI thread that you do with
Load and
SubmitChanges operations, so you don’t have to do any thread marshaling yourself.
You can see the full implementation with some (crude) user prompting in the sample application in the download code.
Summary
The built in validation support in WCF RIA Services is one of the more compelling features that makes me want to use WCF RIA Services for most data-oriented situations in Silverlight. The ease of using data annotation attributes to express simple validation rules or custom validation attributes to tie in more complex validations covers a large number of validation scenarios nicely. Add to that the ability to invoke whatever logic you need from your domain service CRUD methods and Invoke methods on the back end and you have a nice end to end solution for handling validation. The fact that WCF RIA Services takes care of adding the validation errors to the entities on the client side and implements INotifyDataErrorInfo on those entities to participate nicely with the data binding features of Silverlight just rounds it all out into a very powerful part of WCF RIA Services.
The complete code for this article can be downloaded here.
About the Author
Brian Noyes is Chief Architect of IDesign, a Microsoft Regional Director, and Connected System MVP. He is a frequent top rated speaker at conferences worldwide including Microsoft TechEd, DevConnections, DevTeach, and others. He is the author of Developing Applications with Windows Workflow Foundation, Smart Client Deployment with ClickOnce, and Data Binding in Windows Forms 2.0. Brian got started programming as a hobby while flying F-14 Tomcats in the U.S. Navy, later turning his passion for code into his current career. You can contact Brian through his blog at http://briannoyes.net/ or on twitter @briannoyes.