- Download source code from here
- Please visit this project site for the latest releases and source code.
Contents
Introduction
In this article, we will focus on how to do data validation with the
Self-Tracking Entity Generator for WPF/Silverlight. 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 the necessary guidance during their data input
tasks and is an important part of any WPF LOB application.
The previous versions of Self-Tracking Entity Generator relied heavily on
another Visual Studio 2010 Extension "Portable Extensible Metadata" for enabling
the functionalities of data validation, which is covered
here. But,
"Portable Extensible Metadata" has its own limitations:
- PEM does not support
CustomValidationAttribute
.
- PEM only supports adding data validation on property level, not
on entity level.
- PEM only supports adding data validation with non-localizable error
messages.
- PEM allows adding the same type of validation on an entity property
multiple times, which almost always makes no sense for validation types such
as
Required
, RegularExpression
, DataFieldLength
, etc.
Fortunately, we have resolved all of the above issues with our current
version. Next, we will cover the auto-generated validation helper methods. After
that, we will discuss the different approaches of adding validation logic on
both client and server sides of our demo application.
Validation Helper Methods
The auto-generated validation helper methods on the client-side consist of
the following members:
- The
TryValidate()
method loops through all data annotation
attributes and all custom validation actions for an entity object. If any
validation fails, this function returns false, otherwise true.
- The
TryValidate(string propertyName)
method loops through all
data annotation attributes and all custom validation actions for the specified
property name of an entity object. If any validation fails, this function
returns false, otherwise true.
- The
TryValidateObjectGraph()
method loops through the whole
object graph and calls TryValidate()
on each entity object. If
any validation fails, this function returns false, otherwise true.
- The public field
SuspendValidation
can be set to true to
temporarily switch off validation when data binding updates occur. However, setting this field does not
affect either method TryValidate()
, TryValidate(string propertyName)
or TryValidateObjectGraph()
.
And, the server-side validation helper methods are as follows:
Validate()
loops through all data annotation attributes and
all custom validation actions for an entity object. If any validation fails,
it throws an exception.
ValidateObjectGraph()
loops through the whole object graph
and calls Validate()
on each entity object. If any validation
fails, it throws an exception.
Enabling Validation with the IDataErrorInfo Interface
When binding controls in the view to properties, we can choose to do data
validation through
the IDataErrorInfo
interface. First, if we set the
ValidatesOnDataErrors
property on a data binding to true, the
binding engine will report validation errors from an IDataErrorInfo
implementation on the bound data entity. Second, we can set the property
NotifyOnValidationError
to true if we want to receive the
BindingValidationFailed
event, and lastly, we can set the property
ValidatesOnExceptions
to true when we want to catch other types
of errors, such as data conversion problems. Following is a data binding sample
from the instructor page:
Enabling Validation with the INotifyDataErrorInfo Interface
With the arrival of WPF 4.5, we have a better alternative for data validation: the INotifyDataErrorInfo
interface. To switch reporting validation errors from an IDataErrorInfo
implementation to an INotifyDataErrorInfo
implementation, we
need to set the ValidatesOnNotifyDataErrors
property to true, instead of using
property ValidatesOnDataErrors
:
Following is a screenshot of our demo application that shows some validation
errors. The caption text of the labels comes from the
DisplayAttribute.Name
property of the data-bound property (in the
case shown above, the data-bound property is
CurrentInstructor.Name
), and its color shifts from black to red to
indicate that there are errors. On the right side of each text box, there is a
DescriptionViewer
control that displays an information icon and
shows a text description in a tooltip when the mouse pointer is over the icon.
This text description is from the DisplayAttribute.Description
property of the data-bound property. Lastly, we get a summary of all error
messages from the validation summary control.
After learning how to enable validation on the user interface, we
are going to discuss the different approaches of adding data validation logic
next.
Client-side Data Validation
Client-side data validation metadata is added through the Entity Data
Model Designer, and Self-Tracking Entity Generator will use those information to
generate the actual validation attributes for client-side data validation. Furthermore, we need to
call TryValidate()
, TryValidate(string propertyName)
,
or TryValidateObjectGraph()
before submitting changes to make sure
that all client validation logic passes successfully.
Adding Property Level Validation Through the Entity Data Model Designer
Let us first take a look at how to add property level data validation through
the Entity Data Model Designer. Let's say, we want to add a required field
validation to the Name
property of class Person
. In order to do that, we first need to open the
resource file SchoolModelResource.resx, and add a string resource
called FieldRequiredErrorMessage
as shown below:
After that, open the Entity Data Model Designer of
SchoolModel.edmx and select the Name
property of the
Person
entity. From the "Properties" window, select
"STE Validations".
Next, open the "Self-Tracking Entity Property Validations Editor" window by selecting the collection of
"STE Validations" (highlighted above) and add a Required
validation
condition. To set this validation condition, we need to set either Error Message
or Resource Name and Resource Type fields. The Error Message
field contains a non-localizable error message, while the other two fields
specify
a string resource from a resource file. Please note that fields Resource Name and Resource Type
take precedence when generating the actual validation attribute. So, if all
three fields are set, the Error Message field is simply being
ignored.
For the newly added Required
validation condition, we are going to set the values of its Resource Name
and Resource Type fields.
This is done by selecting either one of these two fields, and click its
ellipsis button. A second modal "Error Message Resource Selection" window will
popup, and we can choose the resource file as SchoolModelResource.resx
,
and resource name as FieldRequiredErrorMessage
, which we have just
created above.
After saving changes for the EDM file SchoolModel.edmx, the T4
template will automatically re-generate all self-tracking entity classes with
the new data annotation attributes shown below.
This completes the steps for adding a required field validation to the Name
property of class Person
.
Next, we will discuss how to add custom validation for an entity property.
Adding Property Level Custom Validation
To add custom validation for an entity property, we need to first define a
custom validation method with the following signature:
public static ValidationResult MethodName(object value, ValidationContext context) {...}
Our custom validation example is to validate whether the
Name
property of the Person
class contains any digits.
First, we are going to create the following custom validation method called
ValidatePersonName()
.
After creating this method, we open the Entity Data Model Designer of
SchoolModel.edmx and select the Name
property of the
Person
entity again. From the "Properties" window, select
"STE Validations" and open the "Self-Tracking Entity Property
Validations Editor" window. After that, add a CustomValidation
condition as
shown below:
For the new CustomValidation
condition, we need to link the values of its Method
and Validator Type
fields to the custom validation method just created above. This is done through the modal "Validator
Type and Method Selection" window, and the Self-Tracking Entity Generator
will automatically search and find the ValidatePersonName()
method. All
we need to do is select that choice and save all changes.
After saving changes for the EDM file SchoolModel.edmx, we can use
the update button from the "STE Settings" popup window to re-generate all self-tracking entity classes with
the new data annotation attributes shown below.
Please note that the custom validation condition is the only one that
can be added on the property level multiple times, all other validation
conditions such as Required
and RegularExpression
, can
be added at most once.
Adding Entity Level Custom Validation
Next, we are going to cover the topic of how to add validation logic on the entity level.
Our first example is to validate whether a course's
start date is earlier or equal to its end date. If not, the course's start date
and end date fields will both get highlighted with the error message: Course end
date cannot be earlier than start date.
To enable this type of validation, we need to first set
ValidateEntityOnPropertyChanged
to true. Since entity level
validation logic is not associated with any entity properties, setting
ValidateEntityOnPropertyChanged
to true makes sure that entity
level validation logic is also get called every time there is an update to an entity property.
After that, we need to define the custom validation method ValidateCourseStartAndEndDate()
as shown below:
After creating the custom validation method, we basically follow the same
steps by opening the Entity Data Model Designer of SchoolModel.edmx and select
the Course
entity. From the "Properties" window, select "STE Validations"
to open the "Self-Tracking Entity Property Validations Editor" window.
Then add a new CustomValidation
condition as shown below:
Here is the code snippet that shows the auto-generated CustomValidationAttribute
associated with the Course
entity class:
And, this screen shows the actual validation error when a user types the
wrong start and end dates.
The custom validation method ValidateCourseStartAndEndDate()
returns a
ValidationResult
object, and its constructor takes two parameters,
an error
message to display to the user, and a collection of member names associated with
the validation result. In our case, the collection of member names includes the
start date and end date, and that is why both course start date and end
date are highlighted when the error occurs.
Our next example shows how to validate whether a course's current enrollment
number is under the class size limit. The difference between this example and the
previous one is that this custom validation is truly entity level validation
as it reports the validation result not on any of its properties.
To set up this custom validation, we need follow the same step as
we outlined before. And the first step is to create a custom validation method
called ValidateEnrollmentLimit()
:
Because this custom validation does not report its validation error through
any of its properties, and because entity level validation errors are not
automatically added to the Errors
collection of the ValidationSummary
control, we have to create and use a WPF attached behavior called
ValidationSummaryBehavior
to specifically listen for any entity level validation
errors from the CurrentCourse
object.
And, here we can see the actual validation error when the number of students
enrolled in a course is over the course's size limit. Please note that the
error message is not associated with any properties, and the error is about the
course itself.
So far, we have finished discussing the different options of adding
validation logic. Our next topic is how to make sure that we do not skip any
client-side validation before submitting changes to the server-side.
Validation with TryValidateObjectGraph()
Validation helper methods TryValidate()
,
TryValidate(string propertyName)
, and
TryValidateObjectGraph()
are used to make sure that all client
validation logic passes successfully before submitting changes to the
server-side. The first helper method takes no parameter, and it loops through
all data annotation attributes and all custom validation actions for an entity
object. If any validation fails, this function returns false; otherwise it
returns true. The second method takes a string value parameter which is the
property name that needs to be validated, and this helper method only validates
against that property specified. The last method
TryValidateObjectGraph()
is similar to the first one, but it loops
through the entire object graph instead of the entity object alone.
The code sample below is from the class InstructorPageViewModel
,
and we verify the list AllInstructors
by calling the method
TryValidateObjectGraph()
on each of its elements to make sure that
we only save changes when every instructor passes validation:
private void OnSubmitAllChangeCommand()
{
try
{
if (!_schoolModel.IsBusy)
{
if (AllInstructors != null)
{
var passedValidation = AllInstructors.All(o => o.TryValidateObjectGraph());
if (!passedValidation) return;
_schoolModel.SaveInstructorChangesAsync();
}
}
}
catch (Exception ex)
{
AppMessages.RaiseErrorMessage.Send(ex);
}
}
Switch off validation with SuspendValidation
Another feature on the client-side is the public field SuspendValidation
. By
setting this field to true, we can skip calling the data validation logic when we
initialize an entity object where we normally do not want to show to user any validation
errors. The following example comes from class InstructorPageViewModel
.
When a user wants to add a new instructor record, a new
Instructor
object is created by first setting the SuspendValidation
field to
true. This ensures that setting the rest of the properties does not trigger any
data validation error. After the object is fully initialized, we can then set
SuspendValidation
back to false so that any subsequence changes by the user
will trigger validation logic.
One last point to remember is that setting SuspendValidation
does not affect either method TryValidate()
,
TryValidate(string propertyName)
, or TryValidateObjectGraph()
. These three methods will still
trigger data validation even if SuspendValidation
is set to true.
private void OnAddInstructorCommand()
{
int newPersonId = AllInstructors.Count > 0
? ((from instructor in AllInstructors select Math.Abs(instructor.PersonId)).Max() + 1) * (-1)
: -1;
CurrentInstructor = new Instructor
{
SuspendValidation = true,
PersonId = newPersonId,
Name = string.Empty,
HireDate = DateTime.Now,
Salary = null
};
CurrentInstructor.SuspendValidation = false;
OnEditCommitInstructorCommand();
}
This concludes our discussion about the different aspects of client-side data validation logic. We are going to move on to the topic of data
validation on the server-side next.
Server-side Data Validation
The two auto-generated server-side validation helper methods are
Validate()
and ValidateObjectGraph()
. Besides that, we
can also add cross-entity validations on the server side where they are needed.
Let us first take a look at how to use these two validation helper methods
next.
Validation with ValidateObjectGraph()
The methods Validate()
and ValidateObjectGraph()
are used on the server-side to make sure that all in-coming update calls pass
the same set of validation logic defined on the client-side. We add this extra
step because the server-side is exposed as WCF Services, and we assume that a
call can come from anywhere. Therefore, a WCF Service also needs to validate
its data first.
Following is the method UpdateInstructor()
from the class
SchoolService
, and we call ValidateObjectGraph()
on
the Instructor
object for both add and update operations. This
validation helper method loops through the whole object graph and calls
Validate()
on each entity object. This essentially repeats the same
set of data validation logic defined on the client-side. If any validation
fails, an exception will be thrown and passed back to the client-side.
Otherwise, we save changes by calling
context.People.ApplyChanges(item)
followed by
context.SaveChanges()
.
public List<object> UpdateInstructor(Instructor item)
{
var returnList = new List<object>();
if (item == null)
{
returnList.Add("Instructor cannot be null.");
returnList.Add(0);
return returnList;
}
try
{
using (var context = new SchoolEntities())
{
switch (item.ChangeTracker.State)
{
case ObjectState.Added:
item.ValidateObjectGraph();
context.People.ApplyChanges(item);
context.SaveChanges();
break;
case ObjectState.Deleted:
var courseExists =
context.Courses.Any(n => n.InstructorId == item.PersonId);
if (courseExists)
{
returnList.Add("Cannot delete, there still " +
"exists course assigned to this instructor.");
returnList.Add(item.PersonId);
return returnList;
}
context.People.ApplyChanges(item);
context.SaveChanges();
break;
default:
item.ValidateObjectGraph();
context.People.ApplyChanges(item);
context.SaveChanges();
break;
}
}
returnList.Add(string.Empty);
returnList.Add(item.PersonId);
}
catch (OptimisticConcurrencyException)
{
var errorMessage = "Instructor " + item.PersonId +
" was modified by another user. " +
"Refresh that item before reapply your changes.";
returnList.Add(errorMessage);
returnList.Add(item.PersonId);
}
catch (Exception ex)
{
Exception exception = ex;
while (exception.InnerException != null)
{
exception = exception.InnerException;
}
var errorMessage = "Instructor " + item.PersonId +
" has error: " + exception.Message;
returnList.Add(errorMessage);
returnList.Add(item.PersonId);
}
return returnList;
}
Cross-Entity Validation
For the same UpdateInstructor()
method shown above, the data
validation for the delete operation is a bit more complicated. There is no need
to call the method ValidateObjectGraph()
. Instead, we perform
cross-entity validations and verify whether there is any course still assigned
to this instructor. If this is true, we send a warning message back to the
client-side saying that we cannot delete this instructor (actual message shown
below). Otherwise, the delete operation will go through to remove that
instructor row from the database.
Wrapping Up
We have finished discussing how to do data validation with the Self-Tracking
Entity Generator for WPF/Silverlight. First, we briefly covered all the
auto-generated validation helper methods available. Then, we talked about the
different properties for enabling validation with either the
IDataErrorInfo
or the INotifyDataErrorInfo
interface. After that, we discussed the different
approaches of adding data validation logic on both client and server sides.
I hope you find this article useful, and please rate and/or leave feedback
below. Thank you!
History
- August, 2012 - Initial release.