- Please visit this project site for the latest releases and source code.
Article Series
This article is the third part of a series on developing a Silverlight business application using Self-tracking Entities, WCF Services, WIF, MVVM Light Toolkit, MEF, and T4 Templates.
Contents
Introduction
In this third part, we will focus on data validation with self-tracking entities. The purpose of using data validation is to make sure that any data is validated before being stored in the database. It provides users with necessary guidance during their data input tasks and is an important part of any Silverlight LOB application. In the first half of this article, we will discuss different approaches of adding data validation on both client and server sides. After that, we will dig a little deeper to show you how this data validation infrastructure is actually implemented.
How to Add a Validation Attribute
WCF RIA Services has built-in support for member/field level validation, entity level validation, and operation level validation. Validation attributes are from the System.ComponentModel.DataAnnotations
namespace, and they include:
RequiredAttribute
: specifies that a data field value is required
StringLengthAttribute
: specifies the minimum and maximum length of characters that are allowed in a data field
RegularExpressionAttribute
: specifies that a data field value must match the specified Regular Expression
RangeAttribute
: specifies the numeric range constraints for the value of a data field
CustomValidationAttribute
: specifies a custom validation method to validate a property or class instance
For the EF auto-generated entity classes, we can add validation attributes on the properties of the metadata class like follows:
[MetadataTypeAttribute(typeof(User.UserMetadata))]
public partial class User
{
internal class UserMetadata
{
protected UserMetadata()
{
}
......
[DataMember]
[Display(Name = "NewPasswordLabel",
ResourceType = typeof(IssueVisionResources))]
[Required(ErrorMessageResourceName = "ValidationErrorRequiredField",
ErrorMessageResourceType = typeof(ErrorResources))]
[RegularExpression("^.*[^a-zA-Z0-9].*$",
ErrorMessageResourceName = "ValidationErrorBadPasswordStrength",
ErrorMessageResourceType = typeof(ErrorResources))]
[StringLength(50, MinimumLength = 12,
ErrorMessageResourceName = "ValidationErrorBadPasswordLength",
ErrorMessageResourceType = typeof(ErrorResources))]
public string NewPassword { get; set; }
......
}
}
The validation attributes on metadata classes will be used for server side validation, and WCF RIA Services will then generate these same validation attributes when client side entity classes are being auto-generated. This makes sure that the same validation logic is shared on both sides.
Unfortunately, the metadata class approach does not work for our sample because we are sharing auto-generated self-tracking entity classes, and a metadata class can only exist on the server side, not the client side. We have to take a different approach by using a VS2010 extension called "Portable Extensible Metadata". It will add metadata to our EF model and let IssueVisionModel.tt auto-generate all the data annotation attributes on the property level for each self-tracking entity class.
Before we discuss how to add metadata to the EF model, let us cover how to add client-side only validation first.
Adding Client-side Only Validation
Client-side only validation applies to client-side only properties defined under the ClientExtension folder of the project IssueVision.Data. One of the examples is the PasswordConfirmation
property of class PasswordResetUser
.
public partial class PasswordResetUser
{
......
[Display(Name = "Confirm password")]
[Required(ErrorMessage = "This field is required.")]
[CustomValidation(typeof(PasswordResetUser), "CheckPasswordConfirmation")]
public string PasswordConfirmation
{
get { return this._passwordConfirmation; }
set
{
if (_passwordConfirmation != value)
{
PropertySetterEntry("PasswordConfirmation");
_passwordConfirmation = value;
PropertySetterExit("PasswordConfirmation", value);
OnPropertyChanged("PasswordConfirmation");
}
}
}
private string _passwordConfirmation;
public static ValidationResult CheckPasswordConfirmation(
string passwordConfirmation, ValidationContext validationContext)
{
PasswordResetUser currentUser =
(PasswordResetUser)validationContext.ObjectInstance;
if (!string.IsNullOrEmpty(currentUser.ActualPassword) &&
!string.IsNullOrEmpty(passwordConfirmation) &&
currentUser.ActualPassword != passwordConfirmation)
{
return new ValidationResult("Passwords do not match.",
new string[] { "PasswordConfirmation" });
}
return ValidationResult.Success;
}
}
Since the property PasswordConfirmation
is only defined on the client side, the validation logic only applies to the client side. And, because the property is not generated by a T4 template, we can simply add data annotation attributes directly to the property itself, including the CustomValidation
attribute.
Adding Validation Through Entity Data Model Designer
Next, we will use the NewPassword
property of the User
class as an example, and walk through the steps to add any necessary data annotation attributes using the Entity Data Model Designer. Here is a list of validation requirements for the NewPassword
property:
- The new password is a required property.
- The password must be at least 12 and at most 50 characters long.
- The password needs to contain at least one special character, e.g., @ or #
First, open the Entity Data Model Designer of IssueVision.edmx and select the NewPassword
property of the entity User
. From the Property window (as shown below), we can specify "New Password" as its display name.
Next, open the "Validations Editor" window by selecting the collection of "Validations" (highlighted above) and add the schema metadata for the three validation conditions.
After saving our changes for the EDM file IssueVision.edmx, the T4 template will automatically generate all self-tracking entity classes with the new data annotation attributes we just added.
One of the limitations of adding validation metadata through the Entity Data Model Designer is that it does not currently support CustomValidationAttribute
. For custom validation support, we have to take a different approach as described below.
Adding Entity Level CustomValidation
For entity level custom validation, we can simply take the partial class approach and add CustomValidation
attributes directly. In our sample application, entity level custom validation logic is located inside the validation folders. Specifically, on the server side, they are under the validation folder of the project IssueVision.Data.Web; on the client side, they are under the validation folder of the project IssueVision.Data. Following is an example from the class Issue
, and we are adding two CustomValidation
attributes to the class itself.
[CustomValidation(typeof(IssueRules), "CheckStatusActive")]
[CustomValidation(typeof(IssueRules), "CheckStatusOpen")]
public partial class Issue
{
......
}
The related custom validation functions, CheckStatusActive()
and CheckStatusOpen()
, are defined in a separate static class called IssueRules
:
public static partial class IssueRules
{
......
public static ValidationResult CheckStatusActive(Issue issue,
ValidationContext validationContext)
{
if (issue.StatusID == IssueVisionServiceConstant.ActiveStatusID &&
issue.AssignedToID == null)
return new ValidationResult("A user is required when the " +
"status is Active. ",
new string[] { "StatusID", "AssignedToID" });
return ValidationResult.Success;
}
public static ValidationResult CheckStatusOpen(Issue issue,
ValidationContext validationContext)
{
if (issue.StatusID == IssueVisionServiceConstant.OpenStatusID &&
issue.AssignedToID != null)
return new ValidationResult("AssignedTo user is not needed when " +
"the status is Open.",
new string[] { "StatusID", "AssignedToID" });
return ValidationResult.Success;
}
}
From the code snippet above, we can see that the custom validation functions first check whether specified conditions are met and return ValidationResult.Success
if everything is OK; otherwise, they return a new ValidationResult
object with an error message. The second parameter of the ValidationResult
object is a string array of all affected properties. For the function CheckStatusActive()
, the affected properties are StatusID
and AssignedToID
. This means that both of these two properties would be highlighted if validation fails.
Adding Property Level CustomValidation
After going over the topic of how to add entity level custom validation, let us take a look at how to define custom validation on property level. Property level custom validation functions are also defined inside the validation folders on both client and server sides. Following is an example about the Email
property of the User
class, and we need to make sure that the email address is valid during user input tasks.
public partial class User
{
partial void InitializeValidationSettings()
{
#if SILVERLIGHT
this.ValidateEntityOnPropertyChanged = false;
#endif
AddPropertyValidationAction("Email", ValidateEmail);
}
#region "Private Validation Methods"
private void ValidateEmail(object value)
{
string email = value as string;
if (email == null) return;
if (Regex.IsMatch(email,
@"^(?("")("".+?""@)|(([0-9a-zA-Z]((\.(?!\.))|[-!#\$" +
@"%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-zA-Z])@))" +
@"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-zA-Z]" +
@"[-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,6}))$") == false)
{
#if SILVERLIGHT
ValidationResult result = new ValidationResult(
"Invalid email address.", new List<string> { "Email" });
this.AddError("Email", result);
#else
FaultReason faultReason = new FaultReason("Invalid email address.");
throw new FaultException(faultReason);
#endif
}
}
#endregion "Private Validation Methods"
}
First, let us take a look at the method InitializeValidationSettings()
. This is a partial method, which means that we can choose not to implement it if we have no custom validation logic. If we have property level custom validation like the User
entity class above, the method InitializeValidationSettings()
is implemented and should include two major functions:
- We need to decide whether to set the property
ValidateEntityOnPropertyChanged
to true
or false
. This property is set to true
if there is any entity level custom validation that needs to be checked every time a property gets changed. For the User
class, we can simply set it to false
because we do not have any entity level validation functions.
- Also, within this partial method, we need to add all our custom validation functions with the method
AddPropertyValidationAction()
. The first parameter of this method is a string value which is the name of the property that needs to be validated, and the second parameter points to a custom validation method. In the code sample above, the custom validation method is ValidateEmail()
.
This custom validation method ValidateEmail()
takes only one parameter, which is the value of the property that needs to be validated. If validation fails, the method behaves differently depending on whether being called from the client or server side.
If this method is being called from the client side, a new ValidationResult
object with an appropriate error message will be created followed by a call to the method AddError()
. This AddError()
method will trigger the Silverlight user interface to highlight the properties that failed validation, with the corresponding error messages.
But, if the method is being called from the server side, a FaultException
with error message will be thrown. This exception will be passed back to the client side where the user gets notified about what is wrong.
So far, we have finished our discussion on how to add validation attributes, and we are going to talk about adding operation level validation next.
How to Add Operation Level Validation
Compared with adding validation attributes, adding operation level validation is relatively simple. There are only two methods: TryValidate()
and Validate()
.
Adding Validation on Client Side
The TryValidate()
method is the one for operation level validation on the client side, and it has two overload forms. The first one takes no parameter. It loops through all data annotation attributes and all custom validation actions. If any validation fails, the function returns false
; otherwise it returns true
. The other overload takes a string value parameter which is the property name that needs to be validated, and the method only validates against that property specified.
private void OnLoginCommand(User g)
{
if (!this._authenticationModel.IsBusy)
{
this.LoginScreenErrorMessage = null;
if (g.TryValidate("Name") && g.TryValidate("Password"))
this._authenticationModel.SignInAsync(g.Name, g.Password);
}
}
The code sample above is from the class LoginFormViewModel
, and it shows that we verify two properties Name
and Password
every time we make a call to SignInAsync(g.Name, g.Password)
. Similarly, the code sample below is from the class MyProfileViewModel
, and we verify CurrentUser
by calling TryValidate()
every time we make a call to save any changes:
private void OnSubmitChangeCommand()
{
try
{
if (!_issueVisionModel.IsBusy)
{
if (this.CurrentUser != null)
{
if (this.CurrentUser.TryValidate())
{
this.CurrentUser.IsUserMaintenance = (byte)0;
this._issueVisionModel.SaveChangesAsync();
}
}
}
}
catch (Exception ex)
{
AppMessages.RaiseErrorMessage.Send(ex);
}
}
Adding Validation on Server Side
On the server side, the only operation level validation method is Validate()
. This method loops through all data annotation attributes and all custom validation actions, and if any validation fails, it will throw an exception, which eventually will be passed back and handled on the client side. Here is an example from the PasswordResetService
class inside the project IssueVision.ST.Web.
public void ResetPassword(PasswordResetUser user)
{
user.Validate();
using (IssueVisionEntities context = new IssueVisionEntities())
{
User foundUser = context.Users.FirstOrDefault(n => n.Name == user.Name);
if (foundUser != null)
{
string currentPasswordAnswerHash =
context.GetPasswordAnswerHash(user.Name).First();
string currentPasswordAnswerSalt =
context.GetPasswordAnswerSalt(user.Name).First();
string passwordAnswerHash =
HashHelper.ComputeSaltedHash(user.PasswordAnswer,
currentPasswordAnswerSalt);
if (string.Equals(user.PasswordQuestion, foundUser.PasswordQuestion,
StringComparison.Ordinal) && string.Equals(passwordAnswerHash,
currentPasswordAnswerHash, StringComparison.Ordinal))
{
string currentPasswordSalt = HashHelper.CreateRandomSalt();
string currentPasswordHash =
HashHelper.ComputeSaltedHash(user.NewPassword, currentPasswordSalt);
currentPasswordAnswerSalt = HashHelper.CreateRandomSalt();
currentPasswordAnswerHash =
HashHelper.ComputeSaltedHash(user.PasswordAnswer,
currentPasswordAnswerSalt);
context.ExecuteFunction("UpdatePasswordHashAndSalt"
, new ObjectParameter("Name", user.Name)
, new ObjectParameter("PasswordHash", currentPasswordHash)
, new ObjectParameter("PasswordSalt", currentPasswordSalt));
context.ExecuteFunction("UpdatePasswordAnswerHashAndSalt"
, new ObjectParameter("Name", user.Name)
, new ObjectParameter("PasswordAnswerHash", currentPasswordAnswerHash)
, new ObjectParameter("PasswordAnswerSalt", currentPasswordAnswerSalt));
}
else
throw new UnauthorizedAccessException(
ErrorResources.PasswordQuestionDoesNotMatch);
}
else
throw new UnauthorizedAccessException(ErrorResources.NoUserFound);
}
}
The operation level validation repeated on the server side does exactly the same thing as what is done on the client side. So, it should never throw an exception. The only reason we add this extra step is because the server side is exposed as WCF Services. We are assuming that a call can come from anywhere, and therefore needs to be validated on server side too.
So far, we have finished our discussion on property level validation, entity level validation, as well as operation level validation. This should be sufficient if you are only interested in how to add validation logic with our current data validation infrastructure. But, if you are still interested in how this infrastructure is implemented, we will cover that next.
Data Validation Infrastructure
The data validation infrastructure mainly consists of three sections of code generated by two T4 templates: IssueVisionClientModel.tt and IssueVisionModel.tt. The client side T4 template IssueVisionClientModel.tt generates an implementation of the INotifyDataErrorInfo
interface as well as all its related helper methods. The other T4 template, IssueVisionModel.tt, generates a set of validation helper methods, including the methods TryValidate()
and Validate()
which we have just seen above. Unlike the code generated by IssueVisionClientModel.tt which is only available on the client side, the code generated by IssueVisionModel.tt is available on both client and server sides.
INotifyDataErrorInfo Interface
As quoted from MSDN documentation, the interface INotifyDataErrorInfo
"enables data entity classes to implement custom validation rules and expose validation results to the user interface. You typically implement this interface to provide asynchronous validation logic such as server-side validation. This interface also supports custom error objects, multiple errors per property, cross-property errors, and entity-level errors." It consists of a property HasErrors
, a method GerErrors()
, and an event ErrorsChanged
, and its definition is listed below:
namespace System.ComponentModel
{
public interface INotifyDataErrorInfo
{
bool HasErrors { get; }
IEnumerable GetErrors(string propertyName);
event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
}
public sealed class DataErrorsChangedEventArgs : EventArgs
{
public DataErrorsChangedEventArgs(string propertyName);
public string PropertyName { get; }
}
}
If you need a detailed description about the interface INotifyDataErrorInfo
, the whitepaper "Implementing Data Validation in Silverlight with INotifyDataErrorInfo" is a good source of reference. Here, I will only briefly go over how it is implemented in our sample.
To implement INotifyDataErrorInfo
, we use a property that is a generic Dictionary
object called ValidationErrors
. This object represents a collection of property names (as keys) and their corresponding lists of ValidationResult
objects.
protected Dictionary<string, List<ValidationResult>> ValidationErrors
{
get
{
if (_validationErrors == null)
{
_validationErrors = new Dictionary<string, List<ValidationResult>>();
}
return _validationErrors;
}
}
private Dictionary<string, List<ValidationResult>> _validationErrors;
With the help of this protected property, the rest of the code is quite easy to understand: the property HasErrors
returns whether ValidationErrors
contains an element or not, and the method GetErrors()
returns the corresponding list of ValidationResult
s that match the passed-in property name.
bool INotifyDataErrorInfo.HasErrors
{
get
{
return this.ValidationErrors.Keys.Count != 0;
}
}
IEnumerable INotifyDataErrorInfo.GetErrors(string propertyName)
{
if (propertyName == null)
{
propertyName = string.Empty;
}
if (this.ValidationErrors.ContainsKey(propertyName))
{
return this.ValidationErrors[propertyName];
}
return null;
}
INotifyDataErrorInfo Helper Method
While INotifyDataErrorInfo
enables data entity classes to implement custom validation rules and expose validation results to the user interface, it does not know how to communicate with the rest of the sample application. This is why we also created a set of helper methods through the T4 template IssueVisionClientModel.tt. Here is the list of helper methods:
AddError(string propertyName, ValidationResult validationResult)
: adds a new error into the generic Dictionary
object ValidationErrors
for the property name provided, or for the entity if the property name is String.Empty
/null
. Every time a new error is added, the method also triggers the ErrorsChanged
event so that the user interface gets notified of the change.
ClearErrors(string propertyName)
: removes errors from the generic Dictionary
object ValidationErrors
for the property name provided, or for the entity if the property name is String.Empty
/null
. This method also notifies the user interface through the ErrorsChanged
event whenever there is any change to the ValidationErrors
object.
ClearErrors()
: removes errors from the generic Dictionary
object ValidationErrors
for all property names.
ValidateEntityOnPropertyChanged
: indicates whether or not top-level validation rules must be applied whenever an entity property changes. This property is usually set in the InitializeValidationSettings()
method as we discussed above.
PropertySetterEntry(string propertyName)
: removes any known errors for the provided property name by calling ClearErrors()
.
PropertySetterExit(string propertyName, object propertyValue)
: validates for any known errors for the provided property name.
Please note that the last two methods, PropertySetterEntry()
and PropertySetterExit()
, are partial methods. They are only implemented on the client side, not on the server side. For any primitive property of a self-tracking entity class, these two methods are called immediately before and after setting a new value, as shown below:
[DataMember]
[Required()]
public string Value
{
get { return _value; }
set
{
if (_value != value)
{
ChangeTracker.RecordOriginalValue("Value", _value);
PropertySetterEntry("Value");
_value = value;
PropertySetterExit("Value", value);
OnPropertyChanged("Value");
}
}
}
private string _value;
Whenever the Value
property is set on the client side, a call to PropertySetterEntry("Value")
will clear any known errors. After assigning a new value to the property Value
, PropertySetterExit("Value", value)
will trigger the data validation logic. On the other hand, if the property Value
is set on the server side, no data validation is performed as both PropertySetterEntry()
and PropertySetterExit()
are not implemented.
Validation and Helper Method
Different from the INotifyDataErrorInfo
interface implementation and the related helper methods, the set of validation and helper methods generated by the T4 template IssueVisionModel.tt are available on both client and server sides. We can group the properties and methods into the following list:
- base validation method
Validate(string propertyName, object value)
- client side method
TryValidate()
and server side method Validate()
- partial method definitions
PropertySetterEntry(string propertyName)
, PropertySetterExit(string propertyName, object propertyValue)
, and InitializeValidationSettings()
- property
ValidationActions
and method AddPropertyValidationAction(string propertyName, Action<object> validationAction)
The base validation method Validate(string propertyName, object value)
loops through all related data annotation attributes as well as all related custom validation actions for the specified property name. If propertyName
is String.Empty
/null
, the validation is on the entity level. This method is mainly used by the client side partial method PropertySetterExit()
as follows:
partial void PropertySetterExit(string propertyName, object propertyValue)
{
if (IsDeserializing)
{
return;
}
if (this.ValidateEntityOnPropertyChanged)
{
this.Validate(string.Empty, this);
}
else
{
this.Validate(propertyName, propertyValue);
}
}
For the client side operation level validation method TryValidate()
and the server side operation level validation method Validate()
, we have already covered the usage above, and we will move on to the next in the list, which is the declaration of three partial methods. The partial methods PropertySetterEntry()
and PropertySetterExit()
are only implemented on client side, and always used as a pair inside the primitive property setter definitions of the entity classes. The other partial method InitializeValidationSettings()
is also implemented on client side only, and its implementation is required only if we have custom validation logic for that entity class.
The protected property ValidationActions
is a generic Dictionary
object that keeps a collection of property names (as keys) and their corresponding lists of Action<object>
objects. And, AddPropertyValidationAction(string propertyName, Action<object> validationAction)
is a helper method that adds any custom validation action with their matching property name into the property ValidationActions
. Let us review the code snippet below from the Issue
class to see how the methods InitializeValidationSettings()
and AddPropertyValidationAction()
are used together.
partial void InitializeValidationSettings()
{
#if SILVERLIGHT
this.ValidateEntityOnPropertyChanged = true;
#endif
AddPropertyValidationAction(string.Empty, ValidateStatusResolved);
}
Next Steps
We have finished our discussion about how to add data validation using our enhanced self-tracking entity generator as well as the topic of data validation infrastructure implementation. In our last part, we will move on to cover authentication and authorization using WIF and other remaining topics of interest. I hope you find this article useful, and please rate and/or leave feedback below. Thank you!
History
- February 16, 2011 - Initial release.
- March, 2011 - Update to fix multiple bugs including memory leak issues.