Lesson 5 - Implement Input Validation using IDataErrorInfo

  • 7 minutes to read

Open the previously created project or RegistrationForm.Lesson4 to follow along. The RegistrationForm.Lesson project from the DXEditorsTutorial example 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 an end user then clicks the Register button while specific input data is invalid, the form should display the input errors and disable the Register button. You will 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.

[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)] +
                me[BindableBase.GetPropertyName(() => Gender)];
            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) 
                return RequiredValidationRule.GetErrorMessage(firstNameProp, FirstName);
            else if(columnName == lastNameProp)
                return RequiredValidationRule.GetErrorMessage(lastNameProp, LastName);
            else if(columnName == emailProp)
                return RequiredValidationRule.GetErrorMessage(emailProp, Email);
            else if(columnName == passwordProp)
                return RequiredValidationRule.GetErrorMessage(passwordProp, Password);
            else if(columnName == confirmPasswordProp) {
                if(!string.IsNullOrEmpty(Password) && Password != ConfirmPassword)
                    return "These passwords do not match. Please try again.";
            } else if(columnName == birthdayProp)
                return RequiredValidationRule.GetErrorMessage(birthdayProp, Birthday);
            else if(columnName == genderProp)
                return RequiredValidationRule.GetErrorMessage(genderProp, Gender, -1);
            return null;
        }
    }
}

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.

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

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

gs-editors-501

This issue is related to processing validation using the IDataErrorInfo interface. To fix this issue, it is necessary to not return a validation error in the ViewModel if an end-user did not click the Register button.

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

    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;
            ...
        }
    }
}

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

The validation is almost complete. There is one issue with the Password field. When an end-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.

[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);
    }
    ...
}

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

[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, Gender);
        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);
    }
    ...
}

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

The result is shown below.

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

gs-editors-502

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

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

gs-editors-503

See Also