/// <summary>
/// BindingModel to support validation and handling of error messages
/// (Implements INotifyDataError)
/// </summary>
/// <typeparam name="TBindingModel">Model to bind</typeparam>
public abstract class BindingModelBase<TBindingModel> : INotifyPropertyChanged, INotifyDataErrorInfo
where TBindingModel : BindingModelBase<TBindingModel>
{ private readonly List<PropertyValidation<TBindingModel>> _validations = new List<PropertyValidation<TBindingModel>>();
private Dictionary<string, List<string>> _errorMessages = new Dictionary<string, List<string>>();
protected BindingModelBase()
{ PropertyChanged += (s, e) => { if (e.PropertyName != "HasErrors" && e.PropertyName!="ErrorMessages") ValidateProperty(e.PropertyName); }; }
#region INotifyDataErrorInfo
public IEnumerable GetErrors(string propertyName)
{ if (_errorMessages.ContainsKey(propertyName))
return _errorMessages[propertyName];
return new string[0];
}
public bool HasErrors
{ get { return _errorMessages.Any(); } }
public Dictionary<string, List<string>> ErrorMessages
{ get { return _errorMessages; } }
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged = delegate { };
private void OnErrorsChanged(string propertyName)
{ ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
#endregion
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected void RaisePropertyChanged([CallerMemberName]string propertyName=null)
{ PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
protected void RaisePropertyChanged(Expression<Func<object>> expression)
{eSharper disable ExplicitCallerInfoArgument
if (expression != null) RaisePropertyChanged(GetPropertyName(expression));
eSharper restore ExplicitCallerInfoArgument
}
protected PropertyValidation<TBindingModel> AddValidationFor(Expression<Func<object>> expression)
{ return AddValidationFor(GetPropertyName(expression));
}
protected PropertyValidation<TBindingModel> AddValidationFor(string propertyName)
{ var validation = new PropertyValidation<TBindingModel>(propertyName);
_validations.Add(validation);
return validation;
}
public void ValidateAll()
{ var propertyNamesWithValidationErrors = _errorMessages.Keys;
_errorMessages = new Dictionary<string, List<string>>();
_validations.ForEach(PerformValidation);
var propertyNamesThatMightHaveChangedValidation =
_errorMessages.Keys.Union(propertyNamesWithValidationErrors).ToList();
propertyNamesThatMightHaveChangedValidation.ForEach(OnErrorsChanged);
RaisePropertyChanged(() => HasErrors);
RaisePropertyChanged(() => ErrorMessages);
}
public void ValidateProperty(Expression<Func<object>> expression)
{ ValidateProperty(GetPropertyName(expression));
}
private void ValidateProperty(string propertyName)
{ _errorMessages.Remove(propertyName);
_validations.Where(v => v.PropertyName == propertyName).ToList().ForEach(PerformValidation);
OnErrorsChanged(propertyName);
RaisePropertyChanged(() => HasErrors);
RaisePropertyChanged(() => ErrorMessages);
}
private void PerformValidation(PropertyValidation<TBindingModel> validation)
{ if (validation.IsInvalid((TBindingModel)this))
{ AddErrorMessageForProperty(validation.PropertyName, validation.GetErrorMessage());
}
}
private void AddErrorMessageForProperty(string propertyName, string errorMessage)
{ if (_errorMessages.ContainsKey(propertyName))
{ _errorMessages[propertyName].Add(errorMessage);
}
else
{ _errorMessages.Add(propertyName, new List<string> { errorMessage }); }
}
private static string GetPropertyName(Expression<Func<object>> expression)
{ if (expression == null)
throw new ArgumentNullException("expression");
MemberExpression memberExpression;
if (expression.Body is UnaryExpression)
memberExpression = ((UnaryExpression)expression.Body).Operand as MemberExpression;
else
memberExpression = expression.Body as MemberExpression;
if (memberExpression == null)
throw new ArgumentException("The expression is not a member access expression", "expression");
var property = memberExpression.Member as PropertyInfo;
if (property == null)
throw new ArgumentException("The member access expression does not access a property", "expression");
var getMethod = property.GetGetMethod(true);
if (getMethod.IsStatic)
throw new ArgumentException("The referenced property is a static property", "expression");
return memberExpression.Member.Name;
}
}