Please consider adding this ValidationBase class

Jan 25, 2011 at 1:24 PM

I created a validation base class based on the following article http://weblogs.asp.net/marianor/archive/2009/04/17/wpf-validation-with-attributes-and-idataerrorinfo-interface-in-mvvm.aspx

Usage:

public class MyViewModel : ValidationBase<MyViewModel>
{
	public void DoSomething()
	{
		//..
	}

	public bool CanDoSomething
	{
		get
		{
			return IsValid(() => this.MyValue);
		}
	}

	private string myValue;
	[Required(ErrorMessage = "Required")]
	public string MyValue
	{
		get { return myValue; }
		set { myValue = value; NotifyOfPropertyChange(() => this.MyValue); NotifyOfPropertyChange(() => this.CanDoSomething); }
	}
}

Implementation:

public abstract class ValidationBase<TViewModel> : PropertyChangedBase, IDataErrorInfo where TViewModel : ValidationBase<TViewModel>
{
	private static readonly Dictionary<string, Func<TViewModel, object>>
		propertyGetters = typeof(TViewModel).GetProperties()
							.Where(p => GetValidations(p).Length != 0)
							.ToDictionary(p => p.Name, p => GetValueGetter(p));

	private static readonly Dictionary<string, ValidationAttribute[]> validators =
		typeof(TViewModel).GetProperties()
		.Where(p => GetValidations(p).Length != 0)
		.ToDictionary(p => p.Name, p => GetValidations(p));

	public virtual bool IsValid<TProperty>(Expression<Func<TProperty>> property)
	{
		string name = property.GetMemberInfo().Name;
		var value = propertyGetters[name]((TViewModel)this);
		return validators[name].All(v => v.IsValid(value));
	}

	public string Error
	{
		get
		{
			var errors = from i in validators
							from v in i.Value
							where !v.IsValid(propertyGetters[i.Key]((TViewModel)this))
							select v.ErrorMessage;
			return string.Join(Environment.NewLine, errors.ToArray());
		}
	}

	public string this[string columnName]
	{
		get
		{
			if (propertyGetters.ContainsKey(columnName))
			{
				var value = propertyGetters[columnName]((TViewModel)this);
				var errors = validators[columnName].Where(v => !v.IsValid(value))
					.Select(v => v.ErrorMessage).ToArray();
				NotifyOfPropertyChange(() => this.Error);
				return string.Join(Environment.NewLine, errors);
			}

			NotifyOfPropertyChange(() => this.Error);
			return string.Empty;
		}
	}

	private static ValidationAttribute[] GetValidations(PropertyInfo property)
	{
		return (ValidationAttribute[])property.GetCustomAttributes(typeof(ValidationAttribute), true);
	}

	private static Func<TViewModel, object> GetValueGetter(PropertyInfo property)
	{
		var instance = Expression.Parameter(typeof(TViewModel), "i");
		var cast = Expression.TypeAs(Expression.Property(instance, property), typeof(object));
		return (Func<TViewModel, object>)Expression.Lambda(cast, instance).Compile();
	}
}
Coordinator
Jan 25, 2011 at 5:32 PM

I don't want to add this to the framework. However, I would love to have this as a recipe. Would you consider writing and article that walks through the purpose and function of the code and submitting it? I would be glad to put it up on the site.

Feb 15, 2011 at 12:29 AM

This is really good.  For me it worked to create the implementation as a sub-class of Screen (renamed to ValidatingScreen) from which view models are derived.  Combined with Beth Massi's error template example (http://blogs.msdn.com/b/bethmassi/archive/2008/06/27/displaying-data-validation-messages-in-wpf.aspx) it makes for simple and reusable validation and reporting mechanism for CM.

@EisenbergEffect did loraderon provide you with an article?  If not, let me know and I'll put a short one together showing the validation and separated, standard error display mechanism.

Coordinator
Feb 15, 2011 at 1:55 AM

I haven't received anything, no.

Feb 15, 2011 at 10:12 AM

To anyone thinking about using this example and who needs to use the localization option of ValidationAttribute sub-classes change the two references "v.ErrorMessage" to "v.FormatErrorMessage".  The ErrorMessage property is not used in this scenario so the error and localized message will be lost.  The FormatErrorMessage property will use whatever error string is available.

Feb 15, 2011 at 3:13 PM

@EisenbergEffect

There's post on my blog about using this techique in CM (http://www.lyquidity.com/devblog/?p=71). The post includes an example application which generates the screen shot shown in the post.  You are welcome to make use of any or all of it.

Coordinator
Feb 15, 2011 at 3:23 PM

I added a direct link in the recipes section.

May 4, 2012 at 1:16 PM
Edited May 4, 2012 at 1:24 PM

When I add this to my solution some properties on the XAML window no longer work (WindowStyle=None). Can you look into this please?

Never mind, I figured it out. I was exporting the wrong type in one of my child controls.

Jun 15, 2012 at 11:03 PM

How would we use this with the ValidationContext IsValid?  ValidationContext is always null.

protected override ValidationResult IsValid(object value, ValidationContext

validationContext) {