Click here to Skip to main content
Click here to Skip to main content

Building WPF Applications with Self-Tracking Entity Generator - Data Validation

, 21 Jan 2012 CPOL
Rate this:
Please Sign up or sign in to vote.
This article describes how to do data validation with Self-Tracking Entity Generator for WPF/Silverlight.
  • 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
{
    /// <summary>
    /// Partial method to add all validation actions
    /// defined for this class
    /// </summary>
    partial void InitializeValidationSettings()
    {
        AddPropertyValidationAction("Name", ValidateName);
    }

    #region "Private Validation Methods"

    /// <summary>
    /// Validation Action:
    /// check whether name contains no digit number
    /// </summary>
    /// <param name="value"></param>
    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)
            {
                // 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 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()
{
    // 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().

public List<object> UpdateInstructor(Instructor item)
{
    var returnList = new List<object>();
    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.

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.

License

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

Share

About the Author

Weidong Shen
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

 
QuestionMy vote of 5 PinmemberAhmed.mb29-Feb-12 11:09 
AnswerRe: My vote of 5 PinmemberWeidong Shen1-Mar-12 3:25 
QuestionGood topic PinmemberDean Oliver25-Jan-12 5:10 
AnswerRe: Good topic PinmemberWeidong Shen25-Jan-12 14:46 
QuestionWhich version of entity framework did you use for this? PinmemberVidhya Narasimhan5-Jan-12 7:57 
AnswerRe: Which version of entity framework did you use for this? PinmemberWeidong Shen5-Jan-12 9:02 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141030.1 | Last Updated 21 Jan 2012
Article Copyright 2012 by Weidong Shen
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid