Here is what I have:
In the view there is a tab control with two tabs (sys1 and sys2) each with the same textboxes that are bound to the properties of their respective entities:
sys1:
<TextBox Text="{Binding sys1.Serial, ValidatesOnExceptions=True, NotifyOnValidationError=True}" />
sys2:
<TextBox Text="{Binding sys2.Serial, ValidatesOnExceptions=True, NotifyOnValidationError=True}" />
Using some form of validation I would like to compare the two values and display an error (red border is fine) if the values don't match.
I've used IDataErrorInfo before, but I'm not sure if this type of validation is possible.
Note: whether or not binding directly to the entity is "correct" is a discussion for another place and time. Just know that this is a team project and our teams standards are to bind to the entity so I can't change that unless I have a good reason. Perhaps if it's not possible validating when bound directly to the entity then I may have a good enough reason to change this.
Thanks
I usually expose a Validation Delegate from my Model that my ViewModel can use to attach business-rule validation to the Models.
For example, the ViewModel containing your objects might look like this:
public ParentViewModel()
{
sys1.AddValidationErrorDelegate(ValidateSerial);
sys2.AddValidationErrorDelegate(ValidateSerial);
}
private string ValidateSerial(object sender, string propertyName)
{
if (propertyName == "Serial")
{
if (sys1.Serial == sys2.Serial)
return "Serial already assigned";
}
return null;
}
The idea is that your Model should only contain raw data, therefore it should only validate raw data. This can include validating things like maximum lengths, required fields, and allowed characters. Business Logic, which includes business rules, should be validated in the ViewModel and this allows that to happen.
The actual implementation of my IDataErrorInfo on the Model class would look like this:
#region IDataErrorInfo & Validation Members
/// <summary>
/// List of Property Names that should be validated
/// </summary>
protected List<string> ValidatedProperties = new List<string>();
#region Validation Delegate
public delegate string ValidationErrorDelegate(object sender, string propertyName);
private List<ValidationErrorDelegate> _validationDelegates = new List<ValidationErrorDelegate>();
public void AddValidationErrorDelegate(ValidationErrorDelegate func)
{
_validationDelegates.Add(func);
}
#endregion // Validation Delegate
#region IDataErrorInfo for binding errors
string IDataErrorInfo.Error { get { return null; } }
string IDataErrorInfo.this[string propertyName]
{
get { return this.GetValidationError(propertyName); }
}
public string GetValidationError(string propertyName)
{
// If user specified properties to validate, check to see if this one exists in the list
if (ValidatedProperties.IndexOf(propertyName) < 0)
{
//Debug.Fail("Unexpected property being validated on " + this.GetType().ToString() + ": " + propertyName);
return null;
}
string s = null;
// If user specified a Validation method to use, Validate property
if (_validationDelegates.Count > 0)
{
foreach (ValidationErrorDelegate func in _validationDelegates)
{
s = func(this, propertyName);
if (s != null)
{
return s;
}
}
}
return s;
}
#endregion // IDataErrorInfo for binding errors
#region IsValid Property
public bool IsValid
{
get
{
return (GetValidationError() == null);
}
}
public string GetValidationError()
{
string error = null;
if (ValidatedProperties != null)
{
foreach (string s in ValidatedProperties)
{
error = GetValidationError(s);
if (error != null)
{
return error;
}
}
}
return error;
}
#endregion // IsValid Property
#endregion // IDataErrorInfo & Validation Members
P.S. I see nothing wrong with binding directly to the Model, especially in smaller applications. It may not be the "MVVM-purist" approach, however it is efficient and a lot less work, so I find it a perfectly valid option.
In the set (mutator) for sys1.Serial1 and sys2.Serial you should be able to get the other's value for a comparison.
Related
I'm working on a project using WPF and MVVM with Entity Framework 4.3, and I would like to know how can I perform business logic validation implementing the IDataErrorInfo interface.
All of my models (POCO classes) are implementing it in order to perform raw validations, like maxlength, non-negative numbers, and so on...
But what about bussiness logic validation, such as to prevent duplicate records?
Imagine I have a textbox for a material "reference", which must be unique, defined liked this:
<TextBox Text="{Binding Material.Reference, ValidatesOnDataErrors=True, NotifyOnValidationError=true,
UpdateSourceTrigger=PropertyChanged}">
The model will successfully validate the reference's length, but if there's already a material in my viewmodel's materials observablecollection, how should I notify the user of this fact from my ViewModel, yet taking advantage of the IDataErrorInfo messages?
I've done this in the past by exposing a validation delegate from my models that my ViewModels can hook into for additional business logic validation
The end result ends up looking like this:
public class MyViewModel
{
// Keeping these generic to reduce code here, but they
// should be full properties with PropertyChange notification
public ObservableCollection<MyModel> MyCollection { get; set; }
public MyModel SelectedModel { get; set; }
public MyViewModel()
{
MyCollection = DAL.GetAllModels();
// Add the validation delegate to each object
foreach(var model in MyCollection)
model.AddValidationErrorDelegate(ValidateModel);
}
// Validation Delegate to verify the object's name is unique
private string ValidateObject(object sender, string propertyName)
{
if (propertyName == "Name")
{
var obj = (MyModel)sender;
var existingCount = MyCollection.Count(p =>
p.Name == obj.Name && p.Id != obj.Id);
if (existingCount > 0)
return "This name has already been taken";
}
return null;
}
}
Most of my models inherit from a generic base class, which includes this validation delegate. Here's the relevant code from that base class, taken from my blog article on Validating Business Rules in MVVM
#region IDataErrorInfo & Validation Members
/// <summary>
/// List of Property Names that should be validated.
/// Usually populated by the Model's Constructor
/// </summary>
protected List<string> ValidatedProperties = new List<string>();
#region Validation Delegate
public delegate string ValidationErrorDelegate(
object sender, string propertyName);
private List<ValidationErrorDelegate> _validationDelegates = new List<ValidationErrorDelegate>();
public void AddValidationErrorDelegate(
ValidationErrorDelegate func)
{
_validationDelegates.Add(func);
}
#endregion // Validation Delegate
#region IDataErrorInfo for binding errors
string IDataErrorInfo.Error { get { return null; } }
string IDataErrorInfo.this[string propertyName]
{
get { return this.GetValidationError(propertyName); }
}
public string GetValidationError(string propertyName)
{
// Check to see if this property has any validation
if (ValidatedProperties.IndexOf(propertyName) >= 0)
{
string s = null;
foreach (var func in _validationDelegates)
{
s = func(this, propertyName);
if (s != null)
return s;
}
}
return s;
}
#endregion // IDataErrorInfo for binding errors
#region IsValid Property
public bool IsValid
{
get
{
return (GetValidationError() == null);
}
}
public string GetValidationError()
{
string error = null;
if (ValidatedProperties != null)
{
foreach (string s in ValidatedProperties)
{
error = GetValidationError(s);
if (error != null)
return error;
}
}
return error;
}
#endregion // IsValid Property
#endregion // IDataErrorInfo & Validation Members
This allows me to keep the basic data validation in my Models, and my ViewModels can attach any customized business logic validation they want to the Model as well.
I have a DataGrid in a WPF application which has for its ItemsSource a custom collection that I wrote. The collection enforces that all its items satisfy a certain requirement (namely they must be between some minimum and maximum values).
The collection's class signature is:
public class CheckedObservableCollection<T> : IList<T>, ICollection<T>, IList, ICollection,
INotifyCollectionChanged
where T : IComparable<T>, IEditableObject, ICloneable, INotifyPropertyChanged
I want to be able to use the DataGrid feature in which committing an edit on the last row in the DataGrid results in a new item being added to the end of the ItemsSource.
Unfortunately the DataGrid simply adds a new item created using the default constructor. So, when adding a new item, DataGrid indirectly (through its ItemCollection which is a sealed class) declares:
ItemsSource.Add(new T())
where T is the type of elements in the CheckedObservableCollection. I would like for the grid to instead add a different T, one that satisfies the constraints imposed on the collection.
My questions are: Is there a built in way to do this? Has somebody done this already? What's the best (easiest, fastest to code; performance is not an issue) way to do this?
Currently I just derived DataGrid to override the OnExecutedBeginEdit function with my own as follows:
public class CheckedDataGrid<T> : DataGrid where T : IEditableObject, IComparable<T>, INotifyPropertyChanged, ICloneable
{
public CheckedDataGrid() : base() { }
private IEditableCollectionView EditableItems {
get { return (IEditableCollectionView)Items; }
}
protected override void OnExecutedBeginEdit(ExecutedRoutedEventArgs e) {
try {
base.OnExecutedBeginEdit(e);
} catch (ArgumentException) {
var source = ItemsSource as CheckedObservableCollection<T>;
source.Add((T)source.MinValue.Clone());
this.Focus();
}
}
}
Where MinValue is the smallest allowable item in the collection.
I do not like this solution. If any of you have advice I would be very appreciative!
Thanks
This problem is now semi-solvable under 4.5 using the AddingNewItem event of the DataGrid. Here is my answer to a similar question.
I solved the problem by using DataGrid's AddingNewItem event. This almost entirely undocumented event not only tells you a new item is being added, but also [allows lets you choose which item is being added][2]. AddingNewItem fires before anything else; the NewItem property of the EventArgs is simply null.
Even if you provide a handler for the event, DataGrid will refuse to allow the user to add
rows if the class doesn't have a default constructor. However, bizarrely (but thankfully) if you do have one, and set the NewItem property of the AddingNewItemEventArgs, it will never be called.
If you choose to do this, you can make use of attributes such as [Obsolete("Error", true)] and [EditorBrowsable(EditorBrowsableState.Never)] in order to make sure no one ever invokes the constructor. You can also have the constructor body throw an exception
Decompiling the control lets us see what's happening in there...
For anybody interested, I ended up solving the problem by just deriving from BindingList<T> instead of ObservableCollection<T>, using my derived class as the ItemsSource in a regular DataGrid:
public class CheckedBindingList<T> : BindingList<T>, INotifyPropertyChanged where T : IEditableObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Predicate<T> _check;
private DefaultProvider<T> _defaultProvider;
public CheckedBindingList(Predicate<T> check, DefaultProvider<T> defaultProvider) {
if (check == null)
throw new ArgumentNullException("check cannot be null");
if (defaultProvider != null && !check(defaultProvider()))
throw new ArgumentException("defaultProvider does not pass the check");
_check = check;
_defaultProvider = defaultProvider;
}
/// <summary>
/// Predicate the check item in the list against.
/// All items in the list must satisfy Check(item) == true
/// </summary>
public Predicate<T> Check {
get { return _check; }
set {
if (value != _check) {
RaiseListChangedEvents = false;
int i = 0;
while (i < Items.Count)
if (!value(Items[i]))
++i;
else
RemoveAt(i);
RaiseListChangedEvents = true;
SetProperty(ref _check, value, "Check");
ResetBindings();
}
}
}
public DefaultProvider<T> DefaultProvider {
get { return _defaultProvider; }
set {
if (!_check(value()))
throw new ArgumentException("value does not pass the check");
}
}
protected override void OnAddingNew(AddingNewEventArgs e) {
if (e.NewObject != null)
if (!_check((T)e.NewObject)) {
if (_defaultProvider != null)
e.NewObject = _defaultProvider();
else
e.NewObject = default(T);
}
base.OnAddingNew(e);
}
protected override void OnListChanged(ListChangedEventArgs e) {
switch (e.ListChangedType) {
case (ListChangedType.ItemAdded):
if (!_check(Items[e.NewIndex])) {
RaiseListChangedEvents = false;
RemoveItem(e.NewIndex);
if (_defaultProvider != null)
InsertItem(e.NewIndex, _defaultProvider());
else
InsertItem(e.NewIndex, default(T));
RaiseListChangedEvents = true;
}
break;
case (ListChangedType.ItemChanged):
if (e.NewIndex >= 0 && e.NewIndex < Items.Count) {
if (!_check(Items[e.NewIndex])) {
Items[e.NewIndex].CancelEdit();
throw new ArgumentException("item did not pass the check");
}
}
break;
default:
break;
}
base.OnListChanged(e);
}
protected void SetProperty<K>(ref K field, K value, string name) {
if (!EqualityComparer<K>.Default.Equals(field, value)) {
field = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
This class is incomplete, but the implementation above is enough for validating lists of statically-typed (not built by reflection or with the DLR) objects or value types.
I'm trying to find an easy way to validate over a collection of ViewModels, using the IDataErrorInfo Interface.
I have a ListBox, which is bound to an ObservableCollection of ViewModels.
1 Class "DataView<VMUser>" with an ObservableCollection<VMUser>
1 ViewModel Class "VMUser"
If I implement the IDataErrorInfo into my ViewModel, i can validate for example if the Age > 21 and so on... But i cannot validate if there is no other user with the same email for example, because the ViewModels don't know anything from each other.
I didn't find a way to force the Bindings in my VMUser-DataTemplate to use the IDataErrorInfo of the DataView Class. (without clicking the OK-Button...)
For validation that is based on business rules, I usually expose a Validation Delegate that my ViewModel can set.
For example, the ViewModel containing your collection might look like this:
public ParentViewModel()
{
foreach(var user in UserCollection)
user.AddValidationErrorDelegate(ValidateUser);
}
private string ValidateUser(object sender, string propertyName)
{
if (propertyName == "Email")
{
var user = (UserVM)sender;
if (UserCollection.Count(p => p.Email== user.Email) > 1)
return "Another user already has this Email Address";
}
return null;
}
The idea is that your Model should only contain raw data, therefore it should only validate raw data. This can include validating things like maximum lengths, required fields, and allowed characters. Business Logic, which includes business rules, should be validated in the ViewModel, and this allows that to happen.
The actual implementation of my IDataErrorInfo on the UserVM class would look like this:
#region IDataErrorInfo & Validation Members
/// <summary>
/// List of Property Names that should be validated
/// </summary>
protected List<string> ValidatedProperties = new List<string>();
#region Validation Delegate
public delegate string ValidationErrorDelegate(object sender, string propertyName);
private List<ValidationErrorDelegate> _validationDelegates = new List<ValidationErrorDelegate>();
public void AddValidationErrorDelegate(ValidationErrorDelegate func)
{
_validationDelegates.Add(func);
}
#endregion // Validation Delegate
#region IDataErrorInfo for binding errors
string IDataErrorInfo.Error { get { return null; } }
string IDataErrorInfo.this[string propertyName]
{
get { return this.GetValidationError(propertyName); }
}
public string GetValidationError(string propertyName)
{
// If user specified properties to validate, check to see if this one exists in the list
if (ValidatedProperties.IndexOf(propertyName) < 0)
{
//Debug.Fail("Unexpected property being validated on " + this.GetType().ToString() + ": " + propertyName);
return null;
}
string s = null;
// If user specified a Validation method to use, Validate property
if (_validationDelegates.Count > 0)
{
foreach (ValidationErrorDelegate func in _validationDelegates)
{
s = func(this, propertyName);
if (s != null)
{
return s;
}
}
}
return s;
}
#endregion // IDataErrorInfo for binding errors
#region IsValid Property
public bool IsValid
{
get
{
return (GetValidationError() == null);
}
}
public string GetValidationError()
{
string error = null;
if (ValidatedProperties != null)
{
foreach (string s in ValidatedProperties)
{
error = GetValidationError(s);
if (error != null)
{
return error;
}
}
}
return error;
}
#endregion // IsValid Property
#endregion // IDataErrorInfo & Validation Members
Context
For a WPF application using the MVVM pattern I validate my entity(/business object) using the IDataErrorInfo interface on the entity so that validation rules in my entity are automatically called by WPF and the validationerrors automatically appear in the View. (inspired by Josh Smith in this article: http://joshsmithonwpf.wordpress.com/2008/11/14/using-a-viewmodel-to-provide-meaningful-validation-error-messages/
This works OK for simple validation rules like (name > 10 characters, value must be > 0)
But what to do when the validation rule in the model is more complex (like name must be unique / max value of the property is defined in another entity). I first thought of solving this by let the entity have a reference to a repository, but this doesn't feel good because I think there should only be references from the repository to the entity and not the other way (creating a cyclic reference)
Is it 'legal' to have a reference from the Recipe entity to the ConfigurationRepository. Or do you have a better suggestion?
Do you have suggestions how to implement Entity/Business object validation where the validation is dependent on other Entity/Service, like in the example below.
Below the simplified code of my real world problem.
In the Recipe entity I want to validate that the maximum temperature is less than the value stored in Configuration.MaximumTemperature. How would you solve this?
The Configuration entity (Stores the maximal allowed temperature for a recipe)
public class Configuration: INotifyPropertyChanged, IDataErrorInfo
{
private int _MaxTemperatureSetpoint;
public int MaxTemperatureSetpoint
{
get { return _MaxTemperatureSetpoint; }
set
{
if (value != _MaxTemperatureSetpoint)
{
_Setpoint = value;
RaisePropertyChanged("MaxTemperatureSetpoint");
}
}
}
The Simplified Recipe (Class where the user configures a recipe with a desired temperature (TemperatureSetpoint) and a desired Time (TimeMilliSeconds). The TemperatureSetpoint must be < Configuration.MaxTemperature)
public class Recipe: INotifyPropertyChanged, IDataErrorInfo
{
private int _TemperatureSetpoint;
public int TemperatureSetpoint
{
get { return _TemperatureSetpoint; }
set
{
if (value != _TemperatureSetpoint)
{
_Setpoint = value;
RaisePropertyChanged("Setpoint");
}
}
}
private int _TimeMilliSeconds;
public int TimeMilliSeconds
{
get { return _TimeMilliSeconds; }
set
{
if (value != _TimeMilliSeconds)
{
_TimeMilliSeconds= value;
RaisePropertyChanged("TimeMilliSeconds");
}
}
}
#region IDataErrorInfo Members
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string propertyName]
{
get
{
switch(propertyName)
{
case "TimeMilliSeconds":
//TimeMilliSeconds must be < 30 seconds
if (TimeMilliSeconds < 30000)
{ return "TimeMilliSeconds must be > 0 milliseconds";}
case "TemperatureSetpoint":
//MaxTemperatureSetpoint < maxTemperature stored in the ConfigurationRepository
int maxTemperatureSetpoint = ConfigurationRepository.GetConfiguration().MaxTemperatureSetpoint;
if (TemperatureSetpoint> maxTemperatureSetpoint )
{ return "TemperatureSetpoint must be < " + maxTemperatureSetpoint.ToString();}
}
}
#endregion
}
Recipe Repository
public interface IRecipeRepository
{
/// <summary>
/// Returns the Recipe with the specified key(s) or <code>null</code> when not found
/// </summary>
/// <param name="recipeId"></param>
/// <returns></returns>
TemperatureRecipe Get(int recipeId);
.. Create + Update + Delete methods
}
Configuration Repository
public interface IConfigurationRepository
{
void Configuration GetConfiguration();
}
For validation that is based on business rules, I usually expose a Validation Delegate that my ViewModel can set.
For example, the ViewModel for the Recipe might contain code that looks like this:
public GetRecipe(id)
{
CurrentRecipe = DAL.GetRecipe(id);
CurrentRecipe.AddValidationErrorDelegate(ValidateRecipe);
}
private string ValidateRecipe(string propertyName)
{
if (propertyName == "TemperatureSetpoint")
{
var maxTemp = Configuration.MaxTemperatureSetpoint;
if (CurrentRecipe.TemperatureSetpoint >= maxTemp )
{
return string.Format("Temperature cannot be greater than {0}", maxTemp);
}
}
return null;
}
The idea is that your Model should only contain raw data, therefore it should only validate raw data. This can include validating things like maximum lengths, required fields, and allowed characters. Business Logic, which includes business rules, should be validated in the ViewModel, and this allows that to happen.
The actual implementation of my IDataErrorInfo on the Recipe class would look like this:
#region IDataErrorInfo & Validation Members
/// <summary>
/// List of Property Names that should be validated
/// </summary>
protected List<string> ValidatedProperties = new List<string>();
#region Validation Delegate
public delegate string ValidationErrorDelegate(string propertyName);
private List<ValidationErrorDelegate> _validationDelegates = new List<ValidationErrorDelegate>();
public void AddValidationErrorDelegate(ValidationErrorDelegate func)
{
_validationDelegates.Add(func);
}
#endregion // Validation Delegate
#region IDataErrorInfo for binding errors
string IDataErrorInfo.Error { get { return null; } }
string IDataErrorInfo.this[string propertyName]
{
get { return this.GetValidationError(propertyName); }
}
public string GetValidationError(string propertyName)
{
// If user specified properties to validate, check to see if this one exists in the list
if (ValidatedProperties.IndexOf(propertyName) < 0)
{
//Debug.Fail("Unexpected property being validated on " + this.GetType().ToString() + ": " + propertyName);
return null;
}
string s = null;
// If user specified a Validation method to use, Validate property
if (_validationDelegates.Count > 0)
{
foreach (ValidationErrorDelegate func in _validationDelegates)
{
s = func(propertyName);
if (s != null)
{
return s;
}
}
}
return s;
}
#endregion // IDataErrorInfo for binding errors
#region IsValid Property
public bool IsValid
{
get
{
return (GetValidationError() == null);
}
}
public string GetValidationError()
{
string error = null;
if (ValidatedProperties != null)
{
foreach (string s in ValidatedProperties)
{
error = GetValidationError(s);
if (error != null)
{
return error;
}
}
}
return error;
}
#endregion // IsValid Property
#endregion // IDataErrorInfo & Validation Members
To be honest, I found that the baked in WPF validation methods are not complete and/or elegant enough. I find that using the WPF methods would scatter validation code and logic throughout my application and would even put some in my UI. Like you, I used Custom Business Objects (CBOs) for everything, and I was really wanting to keep my validation in my objects, since I was using them across several projects (a web service, UI, mobile, etc).
What I did was take my CBO (Recipe, in this case), and add some validation methods as properties. Eg:
public Func<string> NameValidation
{
get
{
return () =>
{
string result = null;
if (String.IsNullOrEmpty(Name)) result = "Name cannot be blank";
else if (Name.Length > 100) result = "Name cannot be longer than 100 characters";
return result;
};
}
}
After that, I decorated it with a custom attribute:
[AttributeUsage(AttributeTargets.Property)]
public class CustomValidationMethod : Attribute
{
}
then I created a Validate() method for object-level validation:
public override void Validate()
{
var a = GetType().GetProperties().Where(w => w.GetCustomAttributes(typeof(CustomValidationMethod), true).Length > 0);
foreach (var a2 in a)
{
var result = a2.GetValue(this, null) as Func<string>;
if (result != null)
{
var message = result();
if (message != null)
//There was an error, do something
else if (message == null && Errors.ContainsKey(a2.Name))
//There was no error
}
}
}
then I created custom controls that support my validation. In this case, it was a ComboBox that I derived from the standard ComboBox and added this code:
public Func<string> ValidationMethod
{
get { return (Func<string>) GetValue(ValidationMethodProperty); }
set { SetValue(ValidationMethodProperty, value); }
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (ValidationMethod != null && !String.IsNullOrEmpty(ValidationMethod()))
SetControlAsInvalid();
else
SetControlAsValid();
}
Once this is all set up, I can add field validation in the validation methods (which are stored in my CBOs instead of scattered throughout my code), I can add object-level validation in my Validate() method. As well, I can customize with ease how the control should behave with regards to validation.
To use this, in my VM I would call .Validate() first, then deal with any problems before saving. In my case specifically, I would store error messages in a collection and then query them (this also allowed me to store several error messages instead of the first one)
I have read a lot of Blog post on WPF Validation and on DataAnnotations. I was wondering if there is a clean way to use DataAnnotations as ValidationRules for my entity.
So instead of having this (Source) :
<Binding Path="Age" Source="{StaticResource ods}" ... >
<Binding.ValidationRules>
<c:AgeRangeRule Min="21" Max="130"/>
</Binding.ValidationRules>
</Binding>
Where you must have your
public class AgeRangeRule : ValidationRule
{...}
I want the WPF Binding to go see the Age property and look for DataAnnotation a bit like this:
[Range(1, 120)]
public int Age
{
get { return _age; }
set
{
_age = value;
RaisePropertyChanged<...>(x => x.Age);
}
}
Any ideas if this is possible ?
The closest approach I found is :
// This loop into all DataAnnotations and return all errors strings
protected string ValidateProperty(object value, string propertyName)
{
var info = this.GetType().GetProperty(propertyName);
IEnumerable<string> errorInfos =
(from va in info.GetCustomAttributes(true).OfType<ValidationAttribute>()
where !va.IsValid(value)
select va.FormatErrorMessage(string.Empty)).ToList();
if (errorInfos.Count() > 0)
{
return errorInfos.FirstOrDefault<string>();
}
return null;
Source
public class PersonEntity : IDataErrorInfo
{
[StringLength(50, MinimumLength = 1, ErrorMessage = "Error Msg.")]
public string Name
{
get { return _name; }
set
{
_name = value;
PropertyChanged("Name");
}
}
public string this[string propertyName]
{
get
{
if (porpertyName == "Name")
return ValidateProperty(this.Name, propertyName);
}
}
}
Source and Source
That way, the DataAnnotation works fine, I got a minimum to do on the XAML ValidatesOnDataErrors="True" and it's a fine workaround of Aaron post with the DataAnnotation.
In your model you could implement IDataErrorInfo and do something like this...
string IDataErrorInfo.this[string columnName]
{
get
{
if (columnName == "Age")
{
if (Age < 0 ||
Age > 120)
{
return "You must be between 1 - 120";
}
}
return null;
}
}
You will also need to notify the binding target of the newly defined behavior.
<TextBox Text="{Binding Age, ValidatesOnDataErrors=True}" />
EDIT:
If you only want to use Data Annotations you can follow this blog post which outlines how to accomplish the task.
UPDATE:
Historical representation of the aforementioned link.
Sounds good Aaron. I'm just into WPF and will study databindings next week at work ;) So cannot completely judge your answer...
But with winforms I have used Validation Application Block from the Entlib and implemented IDataErrorInfo (actually IDXDataErrorInfo because we work with DevExpress controls) on a base entity (business object) and that works pretty fine!
It's a bit more sophisticated than the solution you sketched in this way that you place your validation logic on the object and not in the interface implementation. Making it more OOP and maintainable. At the ID(XD)ataErrorInfo I just call Validation.Validate(this), or even better get the validator for the property that the interface is called for and validate the specific validator. Don't forget to call the [SelfValidation] as well because of validation for combinations of properties ;)
You might be interested in the BookLibrary sample application of the WPF Application Framework (WAF). It uses the DataAnnotations Validation attributes together with WPF Binding.
Recently I've had the same idea using the Data Annotation API to validate EF Code First POCO classes in WPF. Like Philippe's post my solution uses reflection, but all necessary code is included in a generic validator.
internal class ClientValidationRule : GenericValidationRule<Client> { }
internal class GenericValidationRule<T> : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
string result = "";
BindingGroup bindingGroup = (BindingGroup)value;
foreach (var item in bindingGroup.Items.OfType<T>()) {
Type type = typeof(T);
foreach (var pi in type.GetProperties()) {
foreach (var attrib in pi.GetCustomAttributes(false)) {
if (attrib is System.ComponentModel.DataAnnotations.ValidationAttribute) {
var validationAttribute = attrib as System.ComponentModel.DataAnnotations.ValidationAttribute;
var val = bindingGroup.GetValue(item, pi.Name);
if (!validationAttribute.IsValid(val)) {
if (result != "")
result += Environment.NewLine;
if (string.IsNullOrEmpty(validationAttribute.ErrorMessage))
result += string.Format("Validation on {0} failed!", pi.Name);
else
result += validationAttribute.ErrorMessage;
}
}
}
}
}
if (result != "")
return new ValidationResult(false, result);
else
return ValidationResult.ValidResult;
}
}
The code above shows a ClientValidatorRule which is derived from the generic GenericValidationRule class. The Client class is my POCO class which will be validated.
public class Client {
public Client() {
this.ID = Guid.NewGuid();
}
[Key, ScaffoldColumn(false)]
public Guid ID { get; set; }
[Display(Name = "Name")]
[Required(ErrorMessage = "You have to provide a name.")]
public string Name { get; set; }
}