wpf-17694-controls-and-libraries-data-editors-getting-started-how-to-create-a-registration-form-lesson-5-implement-input-validation-using-idataerrorinfo.md
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;
}
}
}
<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.
<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.
[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;
...
}
}
}
<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.
[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);
}
...
}
<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.
[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);
}
...
}
<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