2) I have implemented an adapter for DataErrorInfo that could be used for WPF and Silverlight. Note: propertyName = string.Empty stands for an error for the object not directly connected with a property
And in some inherting class:
public class DataErrorInfoAdapter<TError> : INotifyDataErrorInfo, IDataErrorInfo
{
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
private const string ObjectPropertyName = "";
private static readonly TError[] NoErrors = new TError[0];
private readonly Dictionary<string, List<TError>> validationResults;
private readonly Func<string, IEnumerable<TError>> propertyValidation;
private readonly Func<IEnumerable<TError>, string> propertyErrorsToMessage;
public DataErrorInfoAdapter(Func<string, IEnumerable<TError>> propertyValidation, Func<IEnumerable<TError>, string> propertyErrorsToMessage)
{
this.propertyValidation = propertyValidation;
this.propertyErrorsToMessage = propertyErrorsToMessage;
validationResults = new Dictionary<string, List<TError>>();
}
public IEnumerable<TError> CheckErrors(string propertyName)
{
return validationResults[propertyName];
}
public string Error
{
get
{
IEnumerable<TError> errors = GetSetErrors(ObjectPropertyName);
return propertyErrorsToMessage(errors);
}
}
public string this[string propertyName]
{
get
{
// if property validation then validate also the object
if (propertyName != ObjectPropertyName)
{
Validate(ObjectPropertyName);
}
IEnumerable<TError> errors = Validate(propertyName);
return propertyErrorsToMessage(errors);
}
}
public IEnumerable GetErrors(string propertyName)
{
return Validate(propertyName);
}
public bool HasErrors
{
get { return validationResults.Any(); }
}
private void RaiseErrorsChanged(string propertyName)
{
var handler = ErrorsChanged;
if (handler != null)
handler(this, new DataErrorsChangedEventArgs(propertyName));
}
private IEnumerable<TError> Validate(string propertyName)
{
IEnumerable<TError> errors = propertyValidation(propertyName);
SetErrors(propertyName, errors);
return errors;
}
private void SetErrors(string propertyName, IEnumerable<TError> newValidationResults)
{
var localPropertyName = propertyName ?? string.Empty;
var hasCurrentValidationResults = validationResults.ContainsKey(localPropertyName);
var hasNewValidationResults = newValidationResults != null && newValidationResults.Any();
if (hasCurrentValidationResults || hasNewValidationResults)
{
if (hasNewValidationResults)
{
validationResults[localPropertyName] = new List<TError>(newValidationResults);
}
else
{
validationResults.Remove(localPropertyName);
}
RaiseErrorsChanged(localPropertyName);
}
}
private IEnumerable<TError> GetSetErrors(string propertyName)
{
var localPropertyName = propertyName ?? string.Empty;
List<TError> currentValidationResults;
if (validationResults.TryGetValue(localPropertyName, out currentValidationResults))
return currentValidationResults;
else
return NoErrors;
}
}
A sample usage to create a ValidatableViewModel (here an implenetation on top of MVVM Light):public abstract class ValidatableViewModel<TError> : ViewModelBase, INotifyDataErrorInfo, IDataErrorInfo
{
private DataErrorInfoAdapter<TError> dataErrorInfoAdapter;
protected ValidatableViewModel()
{
Initialize();
}
protected ValidatableViewModel(IMessenger messenger)
: base(messenger)
{
Initialize();
}
public void Validate()
{
RaisePropertyChanged(string.Empty);
}
private void Initialize()
{
dataErrorInfoAdapter = new DataErrorInfoAdapter<TError>(Validate, GetFirstErrorString);
dataErrorInfoAdapter.ErrorsChanged += OnErrorsChanged;
}
protected abstract IEnumerable<TError> Validate(string propertyName);
private string GetFirstErrorString(IEnumerable<TError> errors)
{
TError error = errors.FirstOrDefault();
return error.EqualsDefault() ? null : error.ToString();
}
private void OnErrorsChanged(object sender, DataErrorsChangedEventArgs e)
{
RaisePropertyChanged(() => HasErrors);
RaisePropertyChanged(() => Error);
}
#region Interface adaptation
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged
{
add { dataErrorInfoAdapter.ErrorsChanged += value; }
remove { dataErrorInfoAdapter.ErrorsChanged -= value; }
}
public virtual string Error
{
get { return dataErrorInfoAdapter.Error; }
}
public string this[string columnName]
{
get { return dataErrorInfoAdapter[columnName]; }
}
public IEnumerable GetErrors(string propertyName)
{
return dataErrorInfoAdapter.GetErrors(propertyName);
}
public bool HasErrors
{
get { return dataErrorInfoAdapter.HasErrors; }
}
#endregion
}
So now I created a generic viewmodel that supports custom validation.And in some inherting class:
public class SampleViewModel : ValidatableViewModel<ValidationResult> { ... }
we just override the Validate method where you can use FluentValidation, example:protected override IEnumerable<ValidationError> Validate(string propertyName)
{
var result = someFluentValidator.Validate(this, propertyName);
return result.Errors;
}
Please leave me a comment if it is clear and I would be thankful for any comments.