Skip to content

Instantly share code, notes, and snippets.

@davybrion
Created September 15, 2012 13:42
Show Gist options
  • Save davybrion/3727959 to your computer and use it in GitHub Desktop.
Save davybrion/3727959 to your computer and use it in GitHub Desktop.
code snippets for "MVP In Silverlight/WPF: Some Infrastructure Bits" post
public class PropertyValidation<TBindingModel>
where TBindingModel : BindingModel<TBindingModel>
{
private Func<TBindingModel, bool> validationCriteria;
private string errorMessage;
private readonly string propertyName;
public PropertyValidation(string propertyName)
{
this.propertyName = propertyName;
}
public PropertyValidation<TBindingModel> WithMessage(string errorMessage)
{
if (this.errorMessage != null)
{
throw new InvalidOperationException("You can only set the message once.");
}
this.errorMessage = errorMessage;
return this;
}
public PropertyValidation<TBindingModel> When(Func<TBindingModel, bool> validationCriteria)
{
if (this.validationCriteria != null)
{
throw new InvalidOperationException("You can only set the validation criteria once.");
}
this.validationCriteria = validationCriteria;
return this;
}
public bool IsInvalid(TBindingModel presentationModel)
{
if (validationCriteria == null)
{
throw new InvalidOperationException("No criteria have been provided for this validation. (Use the 'When(..)' method.)");
}
return validationCriteria(presentationModel);
}
public string GetErrorMessage()
{
if (errorMessage == null)
{
throw new InvalidOperationException("No error message has been set for this validation. (Use the 'WithMessage(..)' method.)");
}
return errorMessage;
}
public string PropertyName
{
get { return propertyName; }
}
}
public abstract class BindingModel<TBindingModel> : INotifyPropertyChanged, INotifyDataErrorInfo
where TBindingModel : BindingModel<TBindingModel>
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged = delegate { };
private readonly List<PropertyValidation<TBindingModel>> validations = new List<PropertyValidation<TBindingModel>>();
private Dictionary<string, List<string>> errorMessages = new Dictionary<string, List<string>>();
protected BindingModel()
{
PropertyChanged += (s, e) => { if (e.PropertyName != "HasErrors") ValidateProperty(e.PropertyName); };
}
public IEnumerable GetErrors(string propertyName)
{
if (errorMessages.ContainsKey(propertyName)) return errorMessages[propertyName];
return new string[0];
}
public bool HasErrors
{
get { return errorMessages.Count > 0; }
}
public void ValidateAll()
{
var propertyNamesWithValidationErrors = errorMessages.Keys;
ClearAllErrorMessages();
validations.ForEach(PerformValidation);
var propertyNamesThatMightHaveChangedValidation = errorMessages.Keys.Union(propertyNamesWithValidationErrors).ToList();
propertyNamesThatMightHaveChangedValidation.ForEach(NotifyErrorsChanged);
NotifyPropertyChanged(m => m.HasErrors);
}
public void ValidateProperty(Expression<Func<TBindingModel, object>> expression)
{
ValidateProperty(GetPropertyName(expression));
}
private void ValidateProperty(string propertyName)
{
ClearErrorMessagesForProperty(propertyName);
validations.Where(v => v.PropertyName == propertyName).ToList().ForEach(PerformValidation);
NotifyErrorsChanged(propertyName);
NotifyPropertyChanged(m => m.HasErrors);
}
protected PropertyValidation<TBindingModel> AddValidationFor(Expression<Func<TBindingModel, object>> expression)
{
var validation = new PropertyValidation<TBindingModel>(GetPropertyName(expression));
validations.Add(validation);
return validation;
}
private void NotifyErrorsChanged(string propertyName)
{
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
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 void ClearAllErrorMessages()
{
errorMessages = new Dictionary<string, List<string>>();
}
private void ClearErrorMessagesForProperty(string propertyName)
{
errorMessages.Remove(propertyName);
}
protected void NotifyPropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
protected void NotifyPropertyChanged(Expression<Func<TBindingModel, object>> expression)
{
NotifyPropertyChanged(GetPropertyName(expression));
}
private static string GetPropertyName(Expression<Func<TBindingModel, object>> 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 InvalidOperationException("You must specify a property!");
}
return memberExpression.Member.Name;
}
}
AddValidationFor(m => m.Name)
.When(m => string.IsNullOrWhiteSpace(m.name))
.WithMessage("name is a required field");
private string name;
public string Name
{
get { return name; }
set
{
name = value;
NotifyPropertyChanged(m => m.Name);
}
}
public abstract class Presenter<TView, TBindingModel>
where TView : IView
where TBindingModel : BindingModel<TBindingModel>, new()
{
protected Presenter(TView view, IEventAggregator eventAggregator, IAsyncRequestDispatcherFactory requestDispatcherFactory)
{
View = view;
EventAggregator = eventAggregator;
RequestDispatcherFactory = requestDispatcherFactory;
BindingModel = new TBindingModel();
}
protected IEventAggregator EventAggregator { get; private set; }
protected IAsyncRequestDispatcherFactory RequestDispatcherFactory { get; private set; }
protected TView View { get; private set; }
public TBindingModel BindingModel { get; private set; }
public virtual void Initialize() {}
protected void PublishRemoteException(ExceptionInfo exceptionInfo)
{
EventAggregator.Publish(new RemoteExceptionOccurredEvent(exceptionInfo));
}
}
public interface IView : IDisposable
{
void Hide();
void Show();
}
public class View : UserControl, IView
{
private dynamic presenter;
private static readonly bool inDesignMode =
(null == Application.Current) || Application.Current.GetType() == typeof(Application);
protected TPresenter CreateAndInitializePresenter<TPresenter>()
{
if (inDesignMode)
{
return default(TPresenter);
}
presenter = IoC.Container.Resolve<TPresenter>(new Dictionary<string, object> { { "view", this } });
presenter.Initialize();
DataContext = presenter.BindingModel;
return presenter;
}
public void Hide()
{
Visibility = Visibility.Collapsed;
}
public void Show()
{
Visibility = Visibility.Visible;
}
public void Dispose()
{
// naive implementation of Dispose, but then again, this is just a sample
IoC.Container.Release(presenter);
CleanUpChildrenOf(this);
}
private void CleanUpChildrenOf(object obj)
{
var dependencyObject = obj as DependencyObject;
if (dependencyObject != null)
{
var count = VisualTreeHelper.GetChildrenCount(dependencyObject);
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(dependencyObject, i);
if (child != null)
{
DisposeIfDisposable(child);
StopIfProgressBar(child);
CleanUpChildrenOf(child);
}
}
}
}
private static void DisposeIfDisposable(DependencyObject child)
{
var disposable = child as IDisposable;
if (disposable != null) disposable.Dispose();
}
private static void StopIfProgressBar(DependencyObject child)
{
var progressBar = child as ProgressBar;
if (progressBar != null) progressBar.IsIndeterminate = false;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment