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.
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:
- 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.
- 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.
- 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
The DisplayItem
class uses the IDataErrorInfo
interface for the error notification. The interface has two properties:
string Error { get; }
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 string
s 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.
[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:
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.
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.
Data Validation
The data validation is triggered by the WPF framework. Each control is bound to a view model property like:
<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:
public string this[string propertyName]
{
get
{
}
}
The validation steps are:
- 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. - 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. - Get all validation attributes of the given property using reflection. The properties are cached for performance reasons.
- 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.
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:
- 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. - 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.
- 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:
- Signal the view model that the update starts. Additional logic or model updates can be performed by overriding the method
UpdatingModelData()
. - 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.
- 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.