DevExpress MVVM Framework. Data validation. Implementing IDataErrorInfo.

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.

5 comment(s)
Mahdi Saffari

Hello,

You've wrote "The default Error implementation here returns an empty string. Change it to provide a custom error message." but if you make the Error return anything other than a null or String.Empty, then IDataErrorInfoHelper.HasErrors(this) will always be true. Please visit the following link and on the end of line 14 you'll see why!!

IDataErrorInfo.Error must always return NULL.

16 May, 2015
Marc Esteve

Neither Fluent Api nor CustomValidation attribute strategy is working for me.

I'm using Winforms and a POCO viewmodel that does not inherit from ViewModelBase. I guess the fact of the inheritance makes it not to work.

So, is not possible to implement validation on a "pure" POCO ViewModel following sttrategies described on this post Could anybody confirm it, please?

Thanks

15 October, 2015
Alisher (DevExpress Support)

@Marc,

In WinForms, we are using a slightly different mechanism to generate a POCO object. Take a look at the Meta-POCO Bindings section in the documentation.devexpress.com documentation article for more information. However, not all its functionality can be used in WinForms.

As for custom implementation, the approach should work in a WinForms project. Would you please submit a Support Ticket and attach a project illustrating why it does not work on your side?

If you have any inquiries regarding Meta-POCO Bindings, please address them directly to our Support Team via Support Center. We will do our best to assist you in resolving them.

19 October, 2015
carlos cardeiro

Hi,

how would you use "IDataErrorInfo " for validation in a ViewModel where you have dependencies between objects not related to model but to some business rules set on the fly?

23 February, 2016
Alex Chuev (DevExpress)

To anyone interested in validation at the view model level - please check our discussion with Carlos in the following thread: www.devexpress.com/.../T347844.

24 February, 2016

Please login or register to post comments.