Back to Devexpress

Lesson 5 - Implement Input Validation using IDataErrorInfo

wpf-17694-controls-and-libraries-data-editors-getting-started-how-to-create-a-registration-form-lesson-5-implement-input-validation-using-idataerrorinfo.md

latest15.9 KB
Original Source

Lesson 5 - Implement Input Validation using IDataErrorInfo

  • Aug 16, 2023
  • 8 minutes to read

Open the previously created project or RegistrationForm.Lesson4 to follow along.

View Example: Create a Registration Form

The RegistrationForm.Lesson5 project contains the results of this lesson.

When the form is initially shown, its Register button should be enabled and no input errors should be displayed. If a user then clicks the Register button while specific input data is invalid, the form will display the input errors and disable the Register button. Implement this logic with the standard IDataErrorInfo interface. Please review the IDataErrorInfo Interface (System.ComponentModel) MDSN article before moving on.

See the IDataErrorInfo interface implementation in the RegistrationViewModel class.

csharp
[POCOViewModel]
public class RegistrationViewModel : IDataErrorInfo {
    ...
    string IDataErrorInfo.Error {
         get {
            IDataErrorInfo me = (IDataErrorInfo)this;
            string error =
                me[BindableBase.GetPropertyName(() => FirstName)] +
                me[BindableBase.GetPropertyName(() => LastName)] +
                me[BindableBase.GetPropertyName(() => Email)] +
                me[BindableBase.GetPropertyName(() => Password)] +
                me[BindableBase.GetPropertyName(() => ConfirmPassword)] +
                me[BindableBase.GetPropertyName(() => Birthday)];
            if (!string.IsNullOrEmpty(error))
                return "Please check inputted data.";
            return null;
        }
    }
    string IDataErrorInfo.this[string columnName] {
        get {
            string firstNameProp = BindableBase.GetPropertyName(() => FirstName);
            string lastNameProp = BindableBase.GetPropertyName(() => LastName);
            string emailProp = BindableBase.GetPropertyName(() => Email);
            string passwordProp = BindableBase.GetPropertyName(() => Password);
            string confirmPasswordProp = BindableBase.GetPropertyName(() => ConfirmPassword);
            string birthdayProp = BindableBase.GetPropertyName(() => Birthday);
            string genderProp = BindableBase.GetPropertyName(() => Gender);
            if (columnName == firstNameProp) {
                if (FirstName == null || string.IsNullOrEmpty(FirstName))
                    return string.Format("You cannot leave the {0} field empty.", firstNameProp);
            } else if (columnName == lastNameProp) {
                if (LastName == null || string.IsNullOrEmpty(LastName))
                    return string.Format("You cannot leave the {0} field empty.", lastNameProp);
            } else if (columnName == emailProp) {
                if (Email == null || string.IsNullOrEmpty(Email))
                    return string.Format("You cannot leave the {0} field empty.", emailProp);
            } else if (columnName == passwordProp) {
                if (Password == null || string.IsNullOrEmpty(Password))
                    return string.Format("You cannot leave the {0} field empty.", passwordProp);
            } else if (columnName == confirmPasswordProp) {
                if (!string.IsNullOrEmpty(Password) && Password != ConfirmPassword)
                    return "These passwords do not match. Please try again.";
            } else if (columnName == birthdayProp) {
                if (Birthday == null || string.IsNullOrEmpty(Birthday.ToString()))
                    return string.Format("You cannot leave the {0} field empty.", birthdayProp);
            } else if (columnName == genderProp) {
                if (Gender == -1)
                    return string.Format("You cannot leave the {0} field empty.", genderProp);
            }
            return null;
        }
    }
}
vb
<POCOViewModel>
Public Class RegistrationViewModel
    Implements IDataErrorInfo

    '...
    Private ReadOnly Property IDataErrorInfo_Error As String Implements IDataErrorInfo.Error
         Get
            Dim [me] As IDataErrorInfo = DirectCast(Me, IDataErrorInfo)
            Dim errorstring As String = [me](BindableBase.GetPropertyName(Function() FirstName)) +
                               [me](BindableBase.GetPropertyName(Function() LastName)) +
                               [me](BindableBase.GetPropertyName(Function() Email)) +
                               [me](BindableBase.GetPropertyName(Function() Password)) +
                               [me](BindableBase.GetPropertyName(Function() ConfirmPassword)) +
                               [me](BindableBase.GetPropertyName(Function() Birthday))
            If Not String.IsNullOrEmpty(errorstring) Then
                Return "Please check inputted data."
            End If
            Return Nothing
         End Get
    End Property
    Private ReadOnly Property Item(ByVal columnName As String) As String Implements IDataErrorInfo.Item
        Get
            Dim firstNameProp As String = BindableBase.GetPropertyName(Function() FirstName)
            Dim lastNameProp As String = BindableBase.GetPropertyName(Function() LastName)
            Dim emailProp As String = BindableBase.GetPropertyName(Function() Email)
            Dim passwordProp As String = BindableBase.GetPropertyName(Function() Password)
            Dim confirmPasswordProp As String = BindableBase.GetPropertyName(Function() ConfirmPassword)
            Dim birthdayProp As String = BindableBase.GetPropertyName(Function() Birthday)
            Dim genderProp As String = BindableBase.GetPropertyName(Function() Gender)
            If Equals(columnName, firstNameProp) Then
                If Equals(FirstName, Nothing) OrElse String.IsNullOrEmpty(FirstName) Then Return String.Format("You cannot leave the {0} field empty.", firstNameProp)
            ElseIf Equals(columnName, lastNameProp) Then
                If Equals(LastName, Nothing) OrElse String.IsNullOrEmpty(LastName) Then Return String.Format("You cannot leave the {0} field empty.", lastNameProp)
            ElseIf Equals(columnName, emailProp) Then
                If Equals(Email, Nothing) OrElse String.IsNullOrEmpty(Email) Then Return String.Format("You cannot leave the {0} field empty.", emailProp)
            ElseIf Equals(columnName, passwordProp) Then
                If Equals(Password, Nothing) OrElse String.IsNullOrEmpty(Password) Then Return String.Format("You cannot leave the {0} field empty.", passwordProp)
            ElseIf Equals(columnName, confirmPasswordProp) Then
                If Not String.IsNullOrEmpty(Password) AndAlso Not Equals(Password, ConfirmPassword) Then Return "These passwords do not match. Please try again."
            ElseIf Equals(columnName, birthdayProp) Then
                If Birthday Is Nothing OrElse String.IsNullOrEmpty(Birthday.ToString()) Then Return String.Format("You cannot leave the {0} field empty.", birthdayProp)
            ElseIf Equals(columnName, genderProp) Then
                If Gender = -1 Then Return String.Format("You cannot leave the {0} field empty.", genderProp)
            End If

            Return Nothing
        End Get
    End Property
End Class

Enable IDataErrorInfo validation in XAML by setting the Binding.ValidatesOnDataErrors parameter to true. Set this binding parameter for each editor in the form, including the ConfirmPassword editor.

xaml
<dxlc:LayoutControl ... >
   ...
    <dxe:TextEdit NullText="FIRST" ValidateOnEnterKeyPressed="True" ValidateOnTextInput="False">
        <dxe:TextEdit.EditValue>
            <Binding Path="FirstName" ValidatesOnDataErrors="True"
                     UpdateSourceTrigger="PropertyChanged" Mode="TwoWay"/>
        </dxe:TextEdit.EditValue>
    </dxe:TextEdit>
   ...
   <dxe:PasswordBoxEdit EditValue="{Binding ConfirmPassword, ValidatesOnDataErrors=True}" 
                        ValidateOnEnterKeyPressed="True" ValidateOnTextInput="True"/>
   ...
</dxlc:LayoutControl>

If you run the sample now, you will see validation errors when the application is started.

This issue is related to input validation and IDataErrorInfo interface implementation. To fix this issue, it is important that a validation error is not returned in the ViewModel if a user did not click the Register button.

csharp
[POCOViewModel]
public class RegistrationViewModel : IDataErrorInfo {
    ...
    public void AddEmployee() {
        string error = EnableValidationAndGetError();
        if(error != null) return;
        EmployeesModelHelper.AddNewEmployee(FirstName, LastName, Email, Password, Birthday.Value);
    }

    bool allowValidation = false;
    string EnableValidationAndGetError() {
        allowValidation = true;
        string error = ((IDataErrorInfo)this).Error;
        if(!string.IsNullOrEmpty(error)) {
            this.RaisePropertiesChanged();
            return error;
        }
        return null;
    }
    string IDataErrorInfo.Error {
        get {
            if(!allowValidation) return null;
            ...
        }
    }
    string IDataErrorInfo.this[string columnName] {
        get {
            if(!allowValidation) return null;
            ...
        }
    }
}
vb
<POCOViewModel>
Public Class RegistrationViewModel
    Implements IDataErrorInfo

    '...
    Public Sub AddEmployee()
        Dim errorstring As String = EnableValidationAndGetError()
        If errorstring IsNot Nothing Then
            Return
        End If
        EmployeesModelHelper.AddNewEmployee(FirstName, LastName, Email, Password, Birthday.Value)
    End Sub

    Private allowValidation As Boolean = False
    Private Function EnableValidationAndGetError() As String
        allowValidation = True
        Dim errorstring As String = DirectCast(Me, IDataErrorInfo).Error
        If Not String.IsNullOrEmpty(errorstring) Then
            Me.RaisePropertiesChanged()
            Return errorstring
        End If
        Return Nothing
    End Function
    Private ReadOnly Property IDataErrorInfo_Error As String Implements IDataErrorInfo.Error
        Get
            If Not allowValidation Then
                Return Nothing
            End If
            '...
        End Get
    End Property
    Public ReadOnly Property IDataErrorInfo_Item(ByVal columnName As String) As String Implements IDataErrorInfo.Item
        Get
            If Not allowValidation Then
                Return Nothing
            End If
            '...
        End Get
    End Property
End Class

RegistrationViewModel does not return an error until a user has clicked the Register button. If input data has an error and the user clicks Register , you do not need to add a record and execute ViewModel validation logic in the EnableValidationAndGetError method. Notice that the EnableValidationAndGetError calls RaisePropertiesChanged. This method is usually invoked to indicate that underlying data is changed; in this case, a line is needed to initiate the validation process.

Validation is almost complete. The remaining issue is with the Password field. When a user modifies the Password field, the ConfirmPassword field does not react. You can call the RaisePropertyChanged method for the ConfirmPassword field when a Password field is changed.

csharp
[POCOViewModel]
public class RegistrationViewModel : IDataErrorInfo {
    ...
    public virtual string Password { get; set; }
    public virtual string ConfirmPassword { get; set; }
    ...
    protected void OnPasswordChanged() {
        this.RaisePropertyChanged(x => x.ConfirmPassword);
    }
    ...
}
vb
<POCOViewModel>
Public Class RegistrationViewModel
    Implements IDataErrorInfo

    '...
    Public Overridable Property Password() As String
    Public Overridable Property ConfirmPassword() As String
    '...
    Protected Sub OnPasswordChanged()
        Me.RaisePropertyChanged(Function(x) x.ConfirmPassword)
    End Sub
    '...
End Class

It is necessary to display a message indicating whether registration has succeeded or failed. The DevExpress.Xpf.Mvvm library provides a Services mechanism to support these tasks in MVVM.

To use these services, you first need to define a service to show message boxes. The DXMessageBoxService is already defined in the MainView level. To retrieve that service from the RegistrationViewModel , use the GetService<T> extension method.

csharp
[POCOViewModel]
public class RegistrationViewModel : IDataErrorInfo {
    ...
    public void AddEmployee() {
        string error = EnableValidationAndGetError();
        if(error != null) {
            OnValidationFailed(error);
            return;
        }
        EmployeesModelHelper.AddNewEmployee(FirstName, LastName, Email, Password, Birthday.Value);
        OnValidationSucceeded();
    }
    void OnValidationSucceeded() {
        this.GetService<IMessageBoxService>().Show("Registration succeeded", "Registration Form", MessageBoxButton.OK);
    }
    void OnValidationFailed(string error) {
        this.GetService<IMessageBoxService>().Show("Registration failed. " + error, "Registration Form", MessageBoxButton.OK);
    }
    ...
}
vb
<POCOViewModel>
Public Class RegistrationViewModel
    Implements IDataErrorInfo

    '...
    Public Sub AddEmployee()
        Dim [error] As String = EnableValidationAndGetError()
        If [error] IsNot Nothing Then
            OnValidationFailed([error])
            Return
        End If
        EmployeesModelHelper.AddNewEmployee(FirstName, LastName, Email, Password, Birthday.Value)
        OnValidationSucceeded()
    End Sub
    Private Sub OnValidationSucceeded()
        Me.GetService(Of IMessageBoxService)().Show("Registration succeeded", "Registration Form", MessageBoxButton.OK)
    End Sub
    Private Sub OnValidationFailed(ByVal [error] As String)
        Me.GetService(Of IMessageBoxService)().Show("Registration failed. " & [error], "Registration Form", MessageBoxButton.OK)
    End Sub
    '...
End Class

The code above declares two methods - OnValidationSucceeded and OnValidationFailed - invoked when validation succeeds or fails, respectively. These methods obtain the IMessageBoxService service that is defined in the view. This service interface provides the Show method for showing a message box.

The result is shown below.

If a user leaves an editor field blank or there is incorrect input data, a corresponding message will appear.

If all input data is correct, the user is notified that registration was successfully completed.

At this point, the registration form is, for all intents and purposes, complete.

See Also

Starting Point

Lesson 1 - Create Layout

Lesson 2 - Bind Data Editors

Lesson 3 - Customize Editors

Lesson 4 - Implement Input Validation using ValidationRules