I'm starting to implement validation in my WPF project via the IDataErrorInfo interface. My business object contains multiple properties with validation info. How do I get a list of ALL the error messages associated with the object. My thought is that thats what the Error property is for, but I cannot track down anyone using this for reporting on multiple properties.
Thanks!
public string this[string property]
{
get {
string msg = null;
switch (property)
{
case "LastName":
if (string.IsNullOrEmpty(LastName))
msg = "Need a last name";
break;
case "FirstName":
if (string.IsNullOrEmpty(LastName))
msg = "Need a first name";
break;
default:
throw new ArgumentException(
"Unrecognized property: " + property);
}
return msg;
}
}
public string Error
{
get
{
return null ;
}
}
Yeah, I see where you could use the indexer. Not a bad way to go I guess. I was really focused on the 'Error' property though. I like the notion of having the errors contained within the business object. I think what I want to do doesnt exist natively, so I just created a dictionary of errors (updated anytime a property changes) on the object and let the Error return a CarriageReturn delimited list of errors, like so :
public string this[string property]
{
get {
string msg = null;
switch (property)
{
case "LastName":
if (string.IsNullOrEmpty(LastName))
msg = "Need a last name";
break;
case "FirstName":
if (string.IsNullOrEmpty(FirstName))
msg = "Need a first name";
break;
default:
throw new ArgumentException(
"Unrecognized property: " + property);
}
if (msg != null && !errorCollection.ContainsKey(property))
errorCollection.Add(property, msg);
if (msg == null && errorCollection.ContainsKey(property))
errorCollection.Remove(property);
return msg;
}
}
public string Error
{
get
{
if(errorCollection.Count == 0)
return null;
StringBuilder errorList = new StringBuilder();
var errorMessages = errorCollection.Values.GetEnumerator();
while (errorMessages.MoveNext())
errorList.AppendLine(errorMessages.Current);
return errorList.ToString();
}
}
I think it is much easier to use the Validation attributes.
class MyBusinessObject {
[Required(ErrorMessage="Must enter customer")]
public string Customer { get; set; }
[Range(10,99, ErrorMessage="Price must be between 10 and 99")]
public decimal Price { get; set; }
// I have also created some custom attributes, e.g. validate paths
[File(FileValidation.IsDirectory, ErrorMessage = "Must enter an importfolder")]
public string ImportFolder { get; set; }
public string this[string columnName] {
return InputValidation<MyBusinessObject>.Validate(this, columnName);
}
public ICollection<string> AllErrors() {
return InputValidation<MyBusinessObject>.Validate(this);
}
}
The helper class InputValidation looks like this
internal static class InputValidation<T>
where T : IDataErrorInfo
{
/// <summary>
/// Validate a single column in the source
/// </summary>
/// <remarks>
/// Usually called from IErrorDataInfo.this[]</remarks>
/// <param name="source">Instance to validate</param>
/// <param name="columnName">Name of column to validate</param>
/// <returns>Error messages separated by newline or string.Empty if no errors</returns>
public static string Validate(T source, string columnName) {
KeyValuePair<Func<T, object>, ValidationAttribute[]> validators;
if (mAllValidators.TryGetValue(columnName, out validators)) {
var value = validators.Key(source);
var errors = validators.Value.Where(v => !v.IsValid(value)).Select(v => v.ErrorMessage ?? "").ToArray();
return string.Join(Environment.NewLine, errors);
}
return string.Empty;
}
/// <summary>
/// Validate all columns in the source
/// </summary>
/// <param name="source">Instance to validate</param>
/// <returns>List of all error messages. Empty list if no errors</returns>
public static ICollection<string> Validate(T source) {
List<string> messages = new List<string>();
foreach (var validators in mAllValidators.Values) {
var value = validators.Key(source);
messages.AddRange(validators.Value.Where(v => !v.IsValid(value)).Select(v => v.ErrorMessage ?? ""));
}
return messages;
}
/// <summary>
/// Get all validation attributes on a property
/// </summary>
/// <param name="property"></param>
/// <returns></returns>
private static ValidationAttribute[] GetValidations(PropertyInfo property) {
return (ValidationAttribute[])property.GetCustomAttributes(typeof(ValidationAttribute), true);
}
/// <summary>
/// Create a lambda to receive a property value
/// </summary>
/// <param name="property"></param>
/// <returns></returns>
private static Func<T, object> CreateValueGetter(PropertyInfo property) {
var instance = Expression.Parameter(typeof(T), "i");
var cast = Expression.TypeAs(Expression.Property(instance, property), typeof(object));
return (Func<T, object>)Expression.Lambda(cast, instance).Compile();
}
private static readonly Dictionary<string, KeyValuePair<Func<T, object>, ValidationAttribute[]>> mAllValidators;
static InputValidation() {
mAllValidators = new Dictionary<string, KeyValuePair<Func<T, object>, ValidationAttribute[]>>();
foreach (var property in typeof(T).GetProperties()) {
var validations = GetValidations(property);
if (validations.Length > 0)
mAllValidators.Add(property.Name,
new KeyValuePair<Func<T, object>, ValidationAttribute[]>(
CreateValueGetter(property), validations));
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With