The IDataErrorInfo interface is the standard mechanism for data validation in WPF and Silverlight. IDataErrorInfo can provide validation rules for each property in isolation or, alternatively, on the entire object. This post presents three distinct IDataErrorInfo implementations.
As an illustrative example, I will use the simple registration form below:
A complete sample is available from http://www.devexpress.com/example=E5151.
Version 13.2.8 extends our POCO to support an automatic IDataErrorInfo implementation. See our post for additional information on POCO View Model support.
Attributes
Attributes are the most straightforward IDataErrorInfo validation approach. Attributes for specifying validation constraints including Required, Range and CustomValidation are available in the System.ComponentModel.DataAnnotations andDevExpress.Mvvm.Native namespaces.
The sign up form ViewModel with validation attributes is as follows:
[POCOViewModel(ImplementIDataErrorInfo = true)]
publicclass SignupViewModel {
[Required(ErrorMessage = "Please enter the first name.")]
publicvirtualstring FirstName { get; set; }
[Required(ErrorMessage = "Please enter the last name.")]
publicvirtualstring LastName { get; set; }
[EmailAddress]
publicvirtualstring Email { get; set; }
[Required(ErrorMessage = "Please enter the password.")]
[MinLength(8, ErrorMessage = "The password must be at least 8 characters long.")]
[MaxLength(20, ErrorMessage = "The password must not exceed the length of 20.")]
[CustomValidation(typeof(SignupViewModel), "CheckPassword")]
publicvirtualstring Password { get; set; }
[Required(ErrorMessage = "Please confirm the password.")]
[MinLength(8, ErrorMessage = "The password must be at least 8 characters long.")]
[MaxLength(20, ErrorMessage = "The password must not exceed the length of 20.")]
[CustomValidation(typeof(SignupViewModel), "CheckPassword")]
publicvirtualstring ConfirmPassword { get; set; }
publicstatic ValidationResult CheckPassword(objectvalue, ValidationContext context) { ... }
}
NOTE: The MinLength, MaxLength and EmailAddress attributes became available in .NET 4.5.
The POCO will implement the IDataErrorInfo interface for you. Make note of the POCOViewModel attribute and its ImplementIDataErrorInfo parameter, which need to be explicitly specified.
Fluent API
Although attributes are convenient in simple scenarios, they quickly become awkward with custom validation logic. The validating method name (CheckPassword in our case) is passed to the attribute as a string, which makes refactoring less pleasant and more error prone.
The problem is even more pronounced when doing localization. Compare passing an error message through an attribute
[Required(ErrorMessageResourceName = "PleaseConfirmPasswordError",
ErrorMessageResourceType = typeof(Resources))]
publicvirtualstring ConfirmPassword { get; set; }
to the same done via the Fluent API
builder.Property(x => x.ConfirmPassword)
.Required(() => Resources.PleaseConfirmPasswordError);
The Fluent API allows specifying metadata properties and constraints while preserving compile-time checks. The previous ViewModel can be rewritten as the following:
[POCOViewModel(ImplementIDataErrorInfo = true)]
publicclass SignupViewModel : ViewModelBase {
static PropertyMetadataBuilder<SignupViewModel, string> AddPasswordCheck(
PropertyMetadataBuilder<SignupViewModel, string> builder) {
return builder.MatchesInstanceRule(vm => vm.Password == vm.ConfirmPassword,
() => "The passwords don't match.")
.MinLength(8, () => "The password must be at least 8 characters long.")
.MaxLength(20, () => "The password must not exceed the length of 20.");
}
publicstaticvoid BuildMetadata(MetadataBuilder<SignupViewModel> builder) {
builder.Property(x => x.FirstName)
.Required(() => "Please enter the first name.");
builder.Property(x => x.LastName)
.Required(() => "Please enter the last name.");
builder.Property(x => x.Email)
.EmailAddressDataType(() => "Please enter a correct email address.");
AddPasswordCheck(builder.Property(x => x.Password))
.Required(() => "Please enter the password.");
AddPasswordCheck(builder.Property(x => x.ConfirmPassword))
.Required(() => "Please confirm the password.");
}
publicvirtualstring FirstName { get; set; }
publicvirtualstring LastName { get; set; }
publicvirtualstring Email { get; set; }
publicvirtualstring Password { get; set; }
publicvirtualstring ConfirmPassword { get; set; }
}
Now, when you change a property name the compiler will require updating the metadata.
Custom implementation
If you need greater control over validation or you can’t use POCO View Models for some reason, you can use the following code generated by the POCO as a basis for your own implementation.
publicclass SignupViewModel : IDataErrorInfo {
...
string IDataErrorInfo.Error {
get { returnstring.Empty; }
}
string IDataErrorInfo.this[string columnName] {
get { return IDataErrorInfoHelper.GetErrorText(this, columnName); }
}
}
The default Error implementation here returns an empty string. Change it to provide a custom error message.
Consuming the ViewModel
To use the resulting ViewModel, we need a View to be aware of our IDataErrorInfo implementation. Out-of-the-box, DevExpress controls support data validation and visual notifications when a user encounters validation errors. Our registration form example illustrates validation in Data Editors, but the Data Grid or Property Grid would do equally well – all support IDataErrorInfo validation. The View is the following:
<dx:DXWindowx:Class="DataValidationSample.MainWindow"
...
DataContext="{dxmvvm:ViewModelSource Type=local:SignupViewModel}">
<dxlayout:LayoutControldxe:ValidationService.IsValidationContainer="True"x:Name="validationContainer">
...
<dxlayout:LayoutItemLabel="Email" ...>
<dxe:TextEditText="{Binding Email, ValidatesOnDataErrors=True,
UpdateSourceTrigger=PropertyChanged}"/>
</dxlayout:LayoutItem>
...
<Button ... IsEnabled="{Binding Path=(dxe:ValidationService.HasValidationError),
ElementName=validationContainer, Converter={dx:NegationConverter}}"/>
</dxlayout:LayoutControl>
</dx:DXWindow>
That is all that’s necessary. Feel free to comment if you have any questions or suggestions.
OTHER RELATED ARTICLES:
- Getting Started with DevExpress MVVM Framework. Commands and View Models.
- DevExpress MVVM Framework. Introduction to Services, DXMessageBoxService and DialogService.
- DevExpress MVVM Framework. Interaction of ViewModels. IDocumentManagerService.
- DevExpress MVVM Framework. Introduction to POCO ViewModels.
- DevExpress MVVM Framework. Interaction of ViewModels. Messenger.
- DevExpress MVVM Framework. Using Scaffolding Wizards for building Views.
- THIS POST: DevExpress MVVM Framework. Data validation. Implementing IDataErrorInfo.