Click here to Skip to main content
15,867,308 members
Articles / Desktop Programming / WPF
Article

Building WPF Applications with Self-Tracking Entity Generator and Visual Studio 2012 - Data Validation

Rate me:
Please Sign up or sign in to vote.
4.93/5 (13 votes)
27 Aug 2012CPOL13 min read 33.4K   26   7
This article describes how to do data validation with Self-Tracking Entity Generator and Visual Studio 2012.
  • Download source code from here
  • Please visit this project site for the latest releases and source code.

Image 1

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:

Image 2

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:

Image 3

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.

Image 4

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:

Image 5

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".

Image 6

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.

Image 7

For the newly added Required validation condition, we are going to set the values of its Resource Name and Resource Type fields.

Image 8

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.

Image 9

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.

Image 10

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().

Image 11

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:

Image 12

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.

Image 13

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.

Image 14

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:

Image 15

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.

Image 16

Then add a new CustomValidation condition as shown below:

Image 17

Image 18

Here is the code snippet that shows the auto-generated CustomValidationAttribute associated with the Course entity class:

Image 19

And, this screen shows the actual validation error when a user types the wrong start and end dates.

Image 20

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():

Image 21

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.

Image 22

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.

Image 23

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:

C#
private void OnSubmitAllChangeCommand()
{
    try
    {
        if (!_schoolModel.IsBusy)
        {
            if (AllInstructors != null)
            {
                // we only save changes when all instructors passed validation
                var passedValidation = AllInstructors.All(o => o.TryValidateObjectGraph());
                if (!passedValidation) return;

                _schoolModel.SaveInstructorChangesAsync();
            }
        }
    }
    catch (Exception ex)
    {
        // notify user if there is any error
        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.

C#
private void OnAddInstructorCommand()
{
    // create a temporary PersonId
    int newPersonId = AllInstructors.Count > 0
        ? ((from instructor in AllInstructors select Math.Abs(instructor.PersonId)).Max() + 1) * (-1)
        : -1;
    // create a new instructor
    CurrentInstructor = new Instructor
    {
        SuspendValidation = true,
        PersonId = newPersonId,
        Name = string.Empty,
        HireDate = DateTime.Now,
        Salary = null
    };
    CurrentInstructor.SuspendValidation = false;
    // and begin edit
    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().

C#
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:
                    // server side validation
                    item.ValidateObjectGraph();
                    // save changes
                    context.People.ApplyChanges(item);
                    context.SaveChanges();
                    break;
                case ObjectState.Deleted:
                    // verify whether there is any course assigned to this instructor
                    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;
                    }
                    // save changes
                    context.People.ApplyChanges(item);
                    context.SaveChanges();
                    break;
                default:
                    // server side validation
                    item.ValidateObjectGraph();
                    // save changes
                    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.

Image 24

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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
Weidong has been an information system professional since 1990. He has a Master's degree in Computer Science, and is currently a MCSD .NET

Comments and Discussions

 
GeneralMy vote of 5 Pin
rho52236-Feb-14 7:23
rho52236-Feb-14 7:23 
GeneralMy vote of 5 Pin
Kanasz Robert19-Sep-12 4:55
professionalKanasz Robert19-Sep-12 4:55 
GeneralRe: My vote of 5 Pin
Weidong Shen19-Sep-12 8:54
Weidong Shen19-Sep-12 8:54 
GeneralMy vote of 5 Pin
Duke Carey4-Sep-12 1:42
professionalDuke Carey4-Sep-12 1:42 
GeneralRe: My vote of 5 Pin
Weidong Shen13-Sep-12 4:28
Weidong Shen13-Sep-12 4:28 
GeneralWell done Pin
Espen Harlinn27-Aug-12 23:30
professionalEspen Harlinn27-Aug-12 23:30 
GeneralRe: Well done Pin
Weidong Shen28-Aug-12 2:22
Weidong Shen28-Aug-12 2:22 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.