Click here to Skip to main content
15,880,967 members
Articles / Desktop Programming / WPF

MVVM Data Validation

Rate me:
Please Sign up or sign in to vote.
5.00/5 (5 votes)
11 Nov 2015CPOL9 min read 19.4K   640   16  
This article describes a validation framework for MVVM based WPF controls.

Introduction

This article describes a base class that can be used for data validation in the MVVM environment. The code example is extending a previous article "Dynamic Columns in a WPF DataGrid Control", and it extends the "Small Application Framework" with the described validation logic.

Background

This article describes the data handling below the GUI layer which validates the user input. The validation is defined by validation attributes on the data item's properties. Each validation rule is implemented as an attribute that is assigned to a property. In my previous articles, the model data was directly bound to the GUI controls. In this solution I encapsulate the model data in a DisplayItem class which is a view model for data item. Usually, the display item has the same properties as the data item, and it is on these properties that the validation will be performed.

Data View Model

The next class diagram shows the UserDisplayItem class as the view model for the UserRow model. Its shows the most interesting classes and their relations:

  • Application layer, DataGridRow: is the row in the grid control that contains the user display item view model as its DataContext
  • ViewModel layer, UserDisplayItem: the user data representation with the first and last name properties., to which the validation attributes are assigned
  • DataModel layer, UserRow: the persistent model class that contains the user data
  • SmallApplicationFramework, DataViewModelBase: the view model base class that contains the data object (the UserRow instance) which data is being displayed. This class contains the validation logic, which notifies the validation errors via the IDataErrorInfo interface's string Error { get; } and this[string propertyName] { get; } properties.

Image 1

So why have this view model between the control and the data item? The same validation attributes could be applied on the model data properties. True, but this solution allows for more flexibility. Some data validation scenarios:

  1. Data grid control: The validation could be done directly on the data items. The data item property has to be set or updated for the for validation to work, this is the typical case for data grid controls.
  2. Edit form: This is a modal dialog that updates the data when the dialog is closed by pressing the OK button. In this case, the data is temporarily stored in the view model, and finally applied to the data object. The validation is performed on the view model's properties.
  3. Complex or calculated data is shown. In this case, the data field is not a member of the data item but derived from other properties. The display and validation of the data must be done by a view model class because it is not a part of the model data.

The third scenario can be applied to the first and second scenario. Because of this reasoning, all data is wrapped with a view model class. This results in a clean architecture. One of the key issues for a clean architecture is to have the same solution for all similar cases.

Error Notification

Image 2

The DisplayItem class uses the IDataErrorInfo interface for the error notification. The interface has two properties:

C#
// Gets an error message indicating what is wrong with this object.
string Error { get; }

// Gets the error message for the property with the given name.
string this[string columnName] { get; }

The data validation uses the second property. The WPF framework automatically calls this property for all properties that are bound to GUI controls and that have the ValidatesOnDataErrors=true flag set in the binding.

An error text is returned when the property contains invalid data. The GUI can display the error strings in various ways. In the sample code, the error is displayed as a tool tip and the control is shown with red border.

Data Validation

The data validation definition is done by assigning validation attributes to the view model's properties.

C#
[Required(ErrorMessage = "Role name must be given")]
[UniqueRoleName(ErrorMessage = "Role name must be unique")]
public string Name { get; set; }

The .NET Framework offers multiple validation attributes like the 'Required' attribute above. These attributes are in the System.ComponentModel.DataAnnotations assembly, in the same namespace. The UniqueRoleName is a custom attribute that checks if the given role name is already present.

Additionally, validation can be done by the view model without the attributes. The validation framework calls a virtual method as well, that can be used for property validation.

Data Retrieval and Update

Data retrieval copies the data from the model into the view model. Data update is the update of the model data with the values from the view model. The DataViewModelBase class offers the functionality to automate these processes.

Data retrieval process is done when the model data is assigned to the view model's DataObject property. The functionality works like the AutoMapper, it scans the model for its properties and tries to find a matching property on the view model. The data will be copied when it can be gotten from the model and can be set on the view model.

The data update process copies the data from the view model to the model. The flag UpdateModelOnPropertyChange regulates if the model is updated every time a property value has changed or not. The automated update can be undesirable when the data is displayed in a dialog, where the data should be updated when the dialog is accepted and closed.

Additional virtual methods allow specialized logic, data retrieval or update. The virtual methods can be overridden to add additional logic when the data is retrieved of updated. The methods are:

C#
protected virtual void RetrievingModelData() { }
protected virtual void RetrievedModelData() { }
protected virtual void UpdatingModelData() { }
protected virtual void UpdatedModelData() { }

Using the Code

The original idea came from this article by Cyle Witruk. This article describes how to use the validation attributes for the error checking.

The Application

The application has two views containing grid controls for the users and the roles. Each view has its own view model, containing the view logic. The view model contains an observable collection containing the display items. The collection is filled at application start up (main window loaded event), and updated when the data is inserted or removed.

Image 3

The sample project has validation in both grid controls. The user grid control requires the first and the last name of an entry to be filled in. The role grid control also requires the role name to be given. Additionally, the role name must be unique in the role table.

Instantiating the DataViewModelBase

The DataViewModelBase is a base class, and therefore a part of the Small Application Framework.

It is a generic class that contains a model data instance. The next class diagram shows how the UserRow and RoleRow data items are encapsulated by the UserDisplayItem and RoleDisplayItem classes. The data instance is accessible via the DataObject property.

Image 4

Data Validation

The data validation is triggered by the WPF framework. Each control is bound to a view model property like:

XML
<DataGridTextColumn Header="Name"
                    Binding="{Binding Name, ValidatesOnDataErrors=True, 
                    UpdateSourceTrigger=LostFocus}"/>

The ValidatesOnDataErrors flag enables the data validation, and the UpdateSourceTrigger type defines when the data is validated.

The data validation is implemented in the property:

C#
public string this[string propertyName]
{
    get
    {
        // Data validation implementation
    }
}

The validation steps are:

  1. Check if the property must be validated. The business logic can (if required temporarily) cancel the property validation. This can be regulated by overriding the PropertyMustBeValidated(string propertyName) method.
  2. Request the view model implementation if the given property is valid. This can be achieved by overriding the PropertyIsValid(string propertyName, out string errorMessage) method.
  3. Get all validation attributes of the given property using reflection. The properties are cached for performance reasons.
  4. Iterate the validators and collect the error results. Track the error state in a dictionary. The dictionary contains the possible errors of all validated properties. The overall validity of the view model is decided if the dictionary contains errors: no errors -> view model is valid.

Custom Validation

Custom validation can be implemented by creating a new class that derives from ValidationAttribute. The logic is placed in the IsValid method.

C#
public class UniqueRoleNameAttribute : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        ValidationResult validationResult = ValidationResult.Success;
        var roleItem = validationContext.ObjectInstance as RoleDisplayItem;
        if (roleItem != null)
        {
            if (RoleBusinessLogic.IsRoleNameUnique(value as string, roleItem.DataObject))
            {
                validationResult = new ValidationResult(this.ErrorMessage);
            }
        }

        return validationResult;
    }
}

The previous code sample shows the uniqueness validation of the role name. The role name that is entered by the user is contained in the value parameter. The validation context contains the reference to the RoleDisplayItem. The ValidationResult class contains the validation error. If the validation is correct, then the validation result is a null value, defined by the static ValidationResult.Success property.

Data Retrieval

The data retrieval mechanism copies the field values from the data item to the view model. The data is retrieved when the data item is assigned to the view model (when the DataObject is set).

The data retrieval steps are:

  1. Signal the view model that the data retrieval starts. This allows the view model to retrieve non property values, like calculated values. This is achieved by overriding the RetrievingModelData() method.
  2. Using reflection, request all readable properties of the data instance. Iterate through the properties and request the property with the same name from the view model. Write the data field value if the view model property can be set.
  3. Signal the view model that the data retrieval has finished for additional view model tasks. This is achieved by overriding the RetrievedModelData() method.

Data Update

When a view model property is set, then the model data is validated and, if the validation revealed no errors, by default automatically updated. The automatic update can be disabled by setting the flag UpdateModelOnPropertyChange to false.

The model update steps are:

  1. Signal the view model that the update starts. Additional logic or model updates can be performed by overriding the method UpdatingModelData().
  2. Update the model by iterating through all view model for all readable properties and updating the model for each property with the same name that can be written to.
  3. Finally, signal the view model that the update is done. The view model can override the UpdatedModelData() method for addition logic.

Conclusion

The DataViewModelBase class can be used for all types of data editing situations. Not only in data grid situation, as shown in the example code, but also for edit forms which I will show in future articles.

This article is the prelude to my next article about command handling, in which I will show a framework that handles the user command controls (buttons, (context) menu items, etc.

Points of Interest

In the section "View Model", I try to explain why it is necessary to have a view model in between the control and the model data. My reasoning is that subsequent alterations of the model data should be buffered, so that it can be applied once when the user presses the OK button. I think that this reasoning is perfectly fine, but the DataSet framework (and I really like the DataSet) offers a way around it.

The DataSet contains a copy of the original data row. Each row modification can be undone by calling the row's RejectChanges() method. Also the modifications of the whole data set can be reset by calling DataSet.RejectChanges(). I recommend to look into this functionality, because it can be very useful when you are using data sets.

License

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


Written By
Technical Lead Seven-Air Gebr. Meyer AG
Switzerland Switzerland
I am a senior Program Manager, working for Seven-Air Gebr. Meyer AG since September 2022.
I started off programming in C++ and the MFC library, and moved on to the .Net and C# world with .Net Framework 1.0 and further. My projects consist of standalone, client-server applications and web applications.

Comments and Discussions

 
-- There are no messages in this forum --