Skip to main content

Lesson 5 - Implement Input Validation using IDataErrorInfo

  • 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.

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

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"/>
        </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.

gs-editors-501

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.

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

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.

[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 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.

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

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.

gs-editors-502

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.

gs-editors-503

See Also