Reputation:
I have an MVVM application which requires data validation. I would like to keep the validation in the model, so the model can be easily reused with other view models without having to duplicate the validation code. Most examples of data validation with WPF either have the validation done in the view model in a MVVM setup, or data validation done in the model when the model is directly bound to the view and a view model is not used.
I would like to have the data validation in my model, have the view model expose my model to the view, and have the view still be able to receive validation feedback from the model. I am planning on either using IDataErrorInfo or Data Annotations for my validation. I have found some examples of this being done, but neither approach seems ideal.
It is accomplished here with Data Annotations, though it took a moderate amount of custom code.
I like this approach with IDataErrorInfo better as it does not involve custom code, however I don't know if I am comfortable with the approach taken, where the entire model is exposed as a single property to the view, versus exposing just the required individual properties.
Is there a better or more recommended way to accomplish this?
Upvotes: 4
Views: 8137
Reputation: 3231
I don't think you can do all validation in the model, and that is the purpose of the ViewModel.
The reason is that in the View you'll probably have some text boxes that bind their Text property to numbers, dates and other data types that are not directly assignable from a string.
If the user clears the box, or is part way through entering a value (maybe they start a number with a minus sign or decimal point, or are editing a date) the TextBox.Text cannot be converted to the right type for WPF to send to the model. So the model is not updated - it still has its old value and thinks that it is valid. If the user hits the Save button, it will save the last valid value, not what is on the screen.
Unless you restrict your UI to controls that are always valid (checkboxes, listboxes, etc) you need a view model to validate values before you can pass them to the model.
Upvotes: 0
Reputation: 49965
The data annotations method you've linked is somewhat redundant as the framework already has the concept of bindable validation rules (see Taking data binding, validation and MVVM to the next level). The only time the data annotations approach will be useful is when validating in a spot that is completely divorced from the UI.
Aside from those two options, using IDataErrorInfo is your only other choice. Note that you are not "exposing the entire model to the view as a single property", rather you are using an interface to expose your custom entity to the binding framework - there's a big difference. If you choose this method then make sure you use resource strings to hold your error messages rather than using hard coded text. The IDataErrorInfo approach is useful if you have different people working on the view and viewmodel - the person doing the view doesn't need to know anything about the specific validations of the viewmodel.
Upvotes: 2
Reputation: 7414
I ended up avoiding the IDataErrorInfo
stuff as it was a royal pain to use. We placed our validation on the models, using attributes.
public class User : ValidatableBase, INotifyPropertyChanged
{
private string password = string.Empty;
public event PropertyChangedEventHandler PropertyChanged;
[ValidateObjectHasValue(
FailureMessage = "E-Mail can not be left blank.",
ValidationMessageType = typeof(ValidationErrorMessage))]
public string Email
{
get
{
return this.email;
}
set
{
this.email = value;
this.OnPropertyChanged("Email");
}
}
[ValidateStringIsGreaterThan(
GreaterThanValue = 6,
ValidateIfMemberValueIsValid = "Email",
FailureMessage = "Password must be greater than 6 characters.",
ValidationMessageType = typeof(ValidationErrorMessage))]
[ValidateStringIsLessThan(
LessThanValue = 20,
ValidateIfMemberValueIsValid = "Email",
FailureMessage = "Password must be less than 20 characters.",
ValidationMessageType = typeof(ValidationErrorMessage))]
public string Password
{
get
{
return this.password;
}
set
{
this.password = value;
this.OnPropertyChanged("Password");
}
}
/// <summary>
///
/// </summary>
/// <param name="propertyName"></param>
protected virtual void OnPropertyChanged(string propertyName = "")
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
In the example above, validation is performed to make sure that the email property is not empty, and the password validation does a range check. The password validation does not fire until the Email
property is considered to be in a valid state.
The validation project is hosted on GitHub and supports a wide-range of things.
Custom Validation Delegates
Intercepting validation results in custom delegates
I've used this in several projects and it works nice. A real big benefit i've gotten from is that I can re-use validation rules across multiple objects now (IDataErrorInfo suffers from a lot of copy/paste) and I can create DataTemplates around what kind of Validation Message the rules provide back. We've got templates for warnings and templates for errors. Gives you a lot of flexibility.
The original intent of the project was to use it for WinRT projects, but it quickly grew in to something I use on my mobile and WPF apps.
Upvotes: 2