Windows Forms has a validation framework built in. WPF defined a new one. Silverlight refined it further. With every platform taking a slightly different approach to validation, it tends to get rather confusing.
Further confusing the issue is the fact that WPF actually has three options:
- ValidatesOnExceptions
- IDataErrorInfo (ValidatesOnDataErrors)
- Data annotations
If you set ValidatesOnExceptions=True within your WPF {Binding}, you will get validation behavior when the setter throws an exception. If you implement IDataErrorInfo in your view model and set ValidatesOnDataErrors=True, then the view model decides when validation errors occur. And if you set data annotations on your view model properties (and put a bit of arcane code in your setters), then the attributes declaratively attach validation rules.
I recommend against ValidatesOnExceptions and data annotations, both for the same reason. If you throw an exception in the setter (which occurs in either option), then the user’s input is kept in the control. It never makes it to the view model where it can be stored. Indeed, the goal is not to store invalid data, but if you don’t store it temporarily, you can’t use it. It just stays in the TextBox or other control until the user fixes it. Your code doesn’t have access to that state, and therefore can’t help the user to resolve the issue. All the while, the control is in a different state than the view model, which makes me very nervous.
So that leaves IDataErrorInfo. In Silverlight, we now have it’s predecessor INotifyDataErrorInfo. Let’s see how to use this interface with Update Controls.
Single responsibility and validation
XAML validation takes place at the DataContext, which in MVVM is the ViewModel. But logically speaking, validation is a domain concept and therefore belongs in the Model. As a responsibility segregation pattern, MVVM is very sensitive to which side of the fence you choose.
Most MVVM implementations put state in the ViewModel. This gives you a place to store user input before it is validated, thus keeping the Model clean. An Update Controls app, however, does not put state in the ViewModel, since other ViewModels could depend upon it. On the plus side, you don't need a message bus to keep ViewModels in sync. On the minus side, you don't have a quarantine area for validation.
The result is that Update Controls guides you to a pattern in which you allow invalid data into the Model, and then present validation errors through the ViewModel. Don’t throw validation errors on input: take the invalid data into the model. Then let the model expose validation through dependent properties. This has the advantage of keeping the presentation responsibilities in the ViewModel, and the domain responsibilities in the Model. It has the disadvantage, however, of letting the Model get dirty. I find the tradeoff acceptable.
public class MyModel
{
private static Regex ValidPhoneNumber = new Regex(@"\([0-9]{3}\) [0-9]{3}-[0-9]{4}");
private Independent<string> _phoneNumber = new Independent<string>("");
public string PhoneNumber
{
get { return _phoneNumber; }
set { _phoneNumber.Value = value; }
}
public bool PhoneNumberIsValid
{
get { return ValidPhoneNumber.IsMatch(_phoneNumber); }
}
}
This model has a PhoneNumber property, which can be set to any string. This property is backed by an Independent field so that Update Controls can track dependencies. The PhoneNumberIsValid property depends upon the phone number. When the phone number matches a regular expression, the validation property is true.
Dependent validation
Update Controls is a dependency tracking library. Validation errors displayed to the user are dependent upon the state of the model. One way to express a dependency is with the Dependent<T> field. Let’s set up a dependent boolean based on the phone number validity.
public class MyViewModel : ViewModelBase
{
private MyModel _model;
private Dependent<bool> _phoneNumberInvalid;
public MyViewModel(MyModel model)
{
_model = model;
// Create a dependent boolean that is true when the phone number is invalid.
_phoneNumberInvalid = new Dependent<bool>(() => !_model.PhoneNumberIsValid);
}
public string PhoneNumber
{
get { return Get(() => _model.PhoneNumber); }
set { _model.PhoneNumber = value; }
}
}
The _phoneNumberInvalid field depends upon the PhoneNumberIsValid model, because that’s what the lambda expression references. When PhoneNumberIsValid changes (based on a change in PhoneNumber), the dependent goes out-of-date. We can hook this event to bring the dependent back up-to-date. We can’t force the update immediately, because things are still changing. Instead, we use the Dispatcher to schedule an update when the current user input event is finished.
// When the dependent goes out-of-date, bring it back up-to-date at the next opportunity.
_phoneNumberInvalid.DependentSentry.Invalidated += delegate
{
Deployment.Current.Dispatcher.BeginInvoke(delegate
{
// Get the new value of the dependent. This brings it up-to-date.
bool phoneNumberIsInvalid = _phoneNumberInvalid;
});
};
A Dependent field begins its life out-of-date, so we have to force an update immediately upon construction. We can take this opportunity to save the initial state in a field.
// Get the initial value of the dependent. This brings it up-to-date.
_phoneNumberWasInvalid = _phoneNumberInvalid;
Now we have all the hooks we need to implement INotifyDataErrorInfo. When the phone number validation state changes, we fire the ErrorsChanged event.
// When the dependent goes out-of-date, bring it back up-to-date at the next opportunity.
_phoneNumberInvalid.DependentSentry.Invalidated += delegate
{
Deployment.Current.Dispatcher.BeginInvoke(delegate
{
// Get the new value of the dependent. This brings it up-to-date.
bool phoneNumberIsInvalid = _phoneNumberInvalid;
// Fire the event if the status has changed.
if (phoneNumberIsInvalid != _phoneNumberWasInvalid && ErrorsChanged != null)
ErrorsChanged(this, new DataErrorsChangedEventArgs("PhoneNumber"));
_phoneNumberWasInvalid = phoneNumberIsInvalid;
});
};
We finish the interface out with the GetErrors and HasErrors methods.
public IEnumerable GetErrors(string propertyName)
{
if (propertyName == "PhoneNumber")
if (_phoneNumberInvalid)
yield return "Please enter (###) ###-####";
}
public bool HasErrors
{
get { return _phoneNumberInvalid; }
}
Validation is the responsibility of the Model. Presentation of errors is a ViewModel responsibility. Like all ViewModel behavior in MVVM, error presentation depends upon the model. We just have to be explicit about it.
From this code, I can see the beginnings of a reusable validation framework within Update Controls. ForView.Wrap could perform all of the steps that we did here. It would just need help determining validation errors for each property. Perhaps a Caliburn-like convention-over-configuration pattern is in order. Please leave a comment if you agree, disagree, or are willing to contribute to the project.
Recent comments
3 years 13 weeks ago
3 years 19 weeks ago
3 years 44 weeks ago
3 years 46 weeks ago
3 years 47 weeks ago
4 years 3 days ago
4 years 3 days ago
4 years 17 weeks ago
4 years 17 weeks ago
4 years 21 weeks ago