- 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. First, 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 partial method
InitializeValidationSettings()
is the place to add all custom validation actions defined for an entity class.
- The
AddPropertyValidationAction(string propertyName, Action<object> validationAction)
method adds a custom validation
action for the specified property name of an entity object.
- The
AddError(string propertyName, ValidationResult validationResult)
method adds a new error for the specified property name of an entity object.
- The public field
SuspendValidation
can be set to true to temporarily switch off data validation. 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 want to validate through the IDataErrorInfo
interface, we set the
ValidatesOnDataErrors
property on the data binding to true. This will ensure that the data binding engine will request error information for the
data-bound property. Additionally, we set the property NotifyOnValidationError
to true because we want to receive the
BindingValidationFailed
event, and then we set the property ValidatesOnExceptions
to true because we want to catch other types
of errors, such as data conversion problems.
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 examining how the user interface with data validation looks like, we are going to discuss the different approaches of adding data validation logic next.
Client-side Data Validation
Client-side data validation logic can be added either through the Entity Data Model Designer or through custom validation actions. 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 Validation Through the Entity Data Model Designer
Let us first take a look at how to add validation through the Entity Data Model Designer. In order to do that, we first need to make sure that all
the system requirements are met. After that, open the Entity Data Model Designer
of SchoolModel.edmx and select the Name
property of the Person
entity. From the "Properties" window shown below, we can
specify the Name
and Description
fields that will be used to generate the Display
attribute for the Person
class.
Next, open the "Validations Editor" window by selecting the collection of "Validations" (highlighted above) and add the schema metadata for the two validation conditions.
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.
One of the limitations of adding validation logic through the Entity Data Model Designer is that it does not currently support
CustomValidationAttribute
. For adding custom validation, we have to take a different approach as described below.
Adding Custom Validation
Custom validation actions are defined inside the validation folders on both client and server sides. Following is an example of validating whether the
Name
property of the Person
class contains digits or not.
public partial class Person
{
partial void InitializeValidationSettings()
{
AddPropertyValidationAction("Name", ValidateName);
}
#region "Private Validation Methods"
private void ValidateName(object value)
{
var name = (string)value;
if (name != null && Regex.IsMatch(name, @"\d"))
{
#if (WPF)
ValidationResult result =
new ValidationResult("This field cannot contain any digit.",
new List<string> { "Name" });
AddError("Name", result);
#else
FaultReason faultReason =
new FaultReason("Name cannot contain any digit.");
throw new FaultException(faultReason);
#endif
}
}
#endregion "Private Validation Methods"
}
First, we can see the partial method InitializeValidationSettings()
, which is where we add all custom
validation actions defined for the Person
class. Since this is a partial method, we can also choose not to implement it if we have no custom validation logic.
Within this partial method, we add all our custom validation actions using another validation helper 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 ValidateName()
.
This custom validation method ValidateName()
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()
. The AddError()
method is another auto-generated validation helper
method and it adds a new error for the Name
property to the Person
object, and this triggers the user interface to highlight
the property that failed validation with the corresponding error message shown below.
But, if the method is being called from the server side, a FaultException
with an 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 discussing the two 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 validation errors to the user. The following example comes from the 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 subsequent changes by the user will trigger the validation logic.
One last point to remember is that setting SuspendValidation
does not affect the methods 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>();
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 the
IDataErrorInfo
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
- 04 January, 2012 - Initial release.
- 20 January, 2012 - Added documentation for
SuspendValidation
.