In this blog post I want to show you a little ViewModel wrapper for entities I’ve built. You can use it to wrap entities who don’t need to support property change notifications, implementing the INotifyPropertyChanged interface. When your model’s entities support INotifyPropertyChanged and IDataErrorInfo, you might find that you don’t need this wrapper, but I would argue that this still simplifies developing application features, such as updating an entity to the latest version from the database (either after a refresh or save command).
The idea is simple, built a class that wraps any object, and which adds the INotifyPropertyChanged and IDataErrorInfo interfaces to it. Normally this would require wrapping the instance, implementing the interfaces and raising the PropertyChanged event in each property’s setter. But using DynamicObject and late binding syntax (dynamic variable declaration) you can save a lot of time by using a little reflection on the wrapped object.
This technique shines when using MVVM, because this pattern works best (even requires) entities to support INotifyPropertyChanged correctly…
DynamicObject has a range of methods used by late binding, and in this wrapper I use the TryGetMember and TrySetMember methods. When you databind to this object, and you bind it to a certain property, then databinding will use TryGetMember to get the properties value, and TrySetMember to update it.
Let'’s start with the class declaration:
public class EntityViewModel<T>
: DynamicObject, INotifyPropertyChanged, IDataErrorInfo
Next we need to add the TryGetMember and TrySetMember methods. Both receive the property name through the binder argument:
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
PropertyInfo pi = AssertProperty(binder.Name);
if (pi != null)
{
result = pi.GetValue(model, null);
return true;
}
result = null;
return false;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
PropertyInfo pi = AssertProperty(binder.Name);
if (pi != null)
{
pi.SetValue(model, value, null);
RaisePropertyChanged(binder.Name);
return true;
}
return false;
}
Both use a PropertyInfo instance to get and set the property’s value. Also note that the TrySetMember method raises the PropertyChanged event:
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
In a previous post I described how you could implement this interface without hard-coded strings, but in this case all strings are passed as arguments…
To make this more efficient, the PropertyInfo information is cached, so we don’t need to reflect on the type each time. I must admit that I should try measuring if this actually speeds things up, but I need the cache anyway when updating the whole instance, more on this later. Caching is done using a little dictionary:
private static Dictionary<Type, Dictionary<string, PropertyInfo>> cache =
new Dictionary<Type, Dictionary<string, PropertyInfo>>();
This static dictionary maps a type to another dictionary of string to PropertyInfo. In the constructor we retrieve the object’s type and initialize the dictionary if necessary:
private T model;
public EntityViewModel(T model)
{
if (model == null)
throw new ArgumentNullException();
this.model = model;
Type type = typeof(T);
if (!cache.ContainsKey(type))
cache.Add(type, new Dictionary<string, PropertyInfo>());
}
This cache is used by the AssertProperty method, which retrieves the PropertyInfo from the cache (and maybe its it to the cache).
private PropertyInfo AssertProperty(string propName)
{
Dictionary<string, PropertyInfo> dict = cache[typeof(T)];
if (!dict.ContainsKey(propName))
{
dict[propName] = typeof(T).GetProperty(propName);
}
return dict[propName];
}
So why go through all of this trouble? To wrap instances that do not support INotifyPropertyChanged. This way any change to an instance will have to pass through the wrapper, notifying any interested party, which is normally the databinding engine. For example, should you have a Category class with automatic properties, then databinding will not function correctly when using two-way data binding. However when you wrap the instance with this wrapper then suddenly it will. To make this wrapping easier I have a little extension method to wrap a collection of these objects into an ObservableCollection:
public static class EntityViewModelExtensions
{
public static ObservableCollection<EntityViewModel<T>> Wrap<T>(this IEnumerable<T> coll)
where T : IObjectWithChangeTracker
{
return new ObservableCollection<EntityViewModel<T>>( coll.Select(el => new EntityViewModel<T>(el)));
}
}
For example, to wrap a List<Category> into ObservableCollection<EntityViewModel<Category>> we can use this (just call Wrap()):
Categories = ProductsBLL.GetCategoryList().Wrap();
Another interface implemented by this class is the IDataErrorInfo interface, which is used to attach errors to entities. For full support your entity should implement this interface too, since the errors should flow between layers. So your business layer should support this interface, and use it for storing validation errors.
Finally, imagine you have a list of entities in your application, and the presentation layer shows a list of items. You call a method which returns a new copy of one of the items. How can you merge this item in the existing list? You could remove the old entry, and add the fresh copy in its place. But should there exist another entry to this object than you will have two instances in memory which will be a very confusing bug. Of you copy the fresh data into the existing entity, which requires code to copy each property (a little like DataSet’s Merge method). With the wrapper you simply replace the wrapper’s Model property which will notify all bindings to update:
public T Model
{
get { return model; }
set { model = value; RaiseAllUpdated(); }
}
internal void RaiseAllUpdated()
{
foreach (var pair in cache[typeof(T)])
{
RaisePropertyChanged(pair.Key);
}
}
This method will raise the PropertyChanged event for each property in it’s cache. This means that only data-bound properties will be updated.
To conclude: here is the complete class
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Dynamic;
using System.Reflection;
namespace U2U.MVVM
{
public class EntityViewModel<T>
: DynamicObject, INotifyPropertyChanged, IDataErrorInfo
{
private static Dictionary<Type, Dictionary<string, PropertyInfo>> cache =
new Dictionary<Type, Dictionary<string, PropertyInfo>>();
private T model;
public T Model
{
get { return model; }
set { model = value; RaiseAllUpdated(); }
}
public EntityViewModel(T model)
{
if (model == null)
throw new ArgumentNullException();
this.model = model;
Type type = typeof(T);
if (!cache.ContainsKey(type))
cache.Add(type, new Dictionary<string, PropertyInfo>());
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
PropertyInfo pi = AssertProperty(binder.Name);
if (pi != null)
{
result = pi.GetValue(model, null);
return true;
}
result = null;
return false;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
PropertyInfo pi = AssertProperty(binder.Name);
if (pi != null)
{
pi.SetValue(model, value, null);
RaisePropertyChanged(binder.Name);
return true;
}
return false;
}
private PropertyInfo AssertProperty(string propName)
{
Dictionary<string, PropertyInfo> dict = cache[typeof(T)];
if (!dict.ContainsKey(propName))
{
dict[propName] = typeof(T).GetProperty(propName);
}
return dict[propName];
}
internal void RaiseAllUpdated()
{
foreach (var pair in cache[typeof(T)])
{
RaisePropertyChanged(pair.Key);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
public string Error
{
get
{
IDataErrorInfo dataErrorInfo = model as IDataErrorInfo;
if (dataErrorInfo != null)
return dataErrorInfo.Error;
else
return null;
}
}
public string this[string propName]
{
get
{
IDataErrorInfo dataErrorInfo = model as IDataErrorInfo;
if (dataErrorInfo != null)
return dataErrorInfo[propName];
else
return null;
}
}
public bool HasErrors
{
get
{
IDataErrorInfo dataErrorInfo = model as IDataErrorInfo;
if (dataErrorInfo != null)
{
bool hasErrors = dataErrorInfo.Error != null;
foreach (var pair in cache[typeof(T)])
{
hasErrors = hasErrors | (dataErrorInfo[pair.Key]!= null) ;
}
return hasErrors;
}
return false;
}
}
}
}
Feel free to give this wrapper a try (at your own risk!), and please let me know should you discover any problems.