DevExpress MVVM Framework. Data validation. Implementing IDataErrorInfo.

WPF Team Blog
18 March 2014
OTHER RELATED ARTICLES:
  1. Getting Started with DevExpress MVVM Framework. Commands and View Models.
  2. DevExpress MVVM Framework. Introduction to Services, DXMessageBoxService and DialogService.
  3. DevExpress MVVM Framework. Interaction of ViewModels. IDocumentManagerService.
  4. DevExpress MVVM Framework. Introduction to POCO ViewModels.
  5. DevExpress MVVM Framework. Interaction of ViewModels. Messenger.
  6. DevExpress MVVM Framework. Using Scaffolding Wizards for building Views.
  7. THIS POST: DevExpress MVVM Framework. Data validation. Implementing IDataErrorInfo. 
  8. DevExpress MVVM Framework. Using DataAnnotation attributes and DevExpress Fluent API.
  9. DevExpress MVVM Framework. Behaviors.
  10. DevExpress MVVM Framework. TaskbarButtonService, ApplicationJumpListService and NotificationService.
  11. DevExpress MVVM Framework. Asynchronous Commands.
  12. DevExpress MVVM Framework. Converters.


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:

initial_256color

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 and DevExpress.Mvvm.Native namespaces.

The sign up form ViewModel with validation attributes is as follows:

[POCOViewModel(ImplementIDataErrorInfo = true)] 
public class SignupViewModel {
[Required(ErrorMessage = "Please enter the first name.")]
public virtual string FirstName { get; set; }
[Required(ErrorMessage = "Please enter the last name.")]
public virtual string LastName { get; set; }
[EmailAddress]
public virtual string 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")]
public virtual string 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")]
public virtual string ConfirmPassword { get; set; }
public static ValidationResult CheckPassword(object value, 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))]
public virtual string 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)] 
public class 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.");
}
public static void 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.");
}
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual string Email { get; set; }
public virtual string Password { get; set; }
public virtual string 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.

public class SignupViewModel : IDataErrorInfo { 
...
string IDataErrorInfo.Error {
get { return string.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:DXWindow x:Class="DataValidationSample.MainWindow" 
...
DataContext="{dxmvvm:ViewModelSource Type=local:SignupViewModel}">
<dxlayout:LayoutControl dxe:ValidationService.IsValidationContainer="True" x:Name="validationContainer">
...
<dxlayout:LayoutItem Label="Email" ...>
<dxe:TextEdit Text="{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.

Free DevExpress Products - Get Your Copy Today

The following free DevExpress product offers remain available. Should you have any questions about the free offers below, please submit a ticket via the DevExpress Support Center at your convenience. We'll be happy to follow-up.
No Comments

Please login or register to post comments.