We often have the following issue with SL4: in the Model we get a collection of entities and in the ViewModel, we want a collection of these entities but with VM intelligence (for example some calculated properties).
In this case the issue is to manage the two collections and we have to propagate modifications in two ways. An idea is to use a “RelayCollection” which does the job.
In .NET, we can add “fake” properties used for binding by implementing ICustomTypeDescriptor but we don’t have it in SL.
However, with SL5, we have a really great solution using ICustomTypeProvider interface.
I based my code on Alexandra one and I tried to improve it a little bit.
So this is my code:
public abstract class DynamicBaseType
{
public abstract object GetPropertyValue(string propertyName);
public abstract void SetPropertyValue(string propertyName, object value);
}
public abstract class DynamicBaseType<T> : DynamicBaseType, ICustomTypeProvider, INotifyPropertyChanged
where T : DynamicBaseType<T>
{
private static List<CustomPropertyInfo> _customProperties = new List<CustomPropertyInfo>();
private Dictionary<string, object> _customPropertyValues;
private CustomType _customtype;
protected DynamicBaseType()
{
_customPropertyValues = new Dictionary<string, object>();
foreach (var property in _customProperties)
_customPropertyValues.Add(property.Name, null);
}
public static void AddProperty(string name, Type type, object value = null, List<Attribute> attributes = null)
{
if (!CheckIfNameExists(name))
_customProperties.Add(new CustomPropertyInfo(name, type, value, attributes));
}
public static void AddProperty<V>(string name, Func<T, V> get, Action<T, V> set = null, List<Attribute> attributes = null, string[] properties = null)
{
if (!CheckIfNameExists(name))
_customProperties.Add(new CustomPropertyInfo<V>(name, get, set, attributes, properties));
}
private static bool CheckIfNameExists(string name)
{
if (_customProperties.Select(p => p.Name).Contains(name) || typeof(T).GetProperties().Select(p => p.Name).Contains(name))
throw new Exception("The property with this name already exists: " + name);
return false;
}
private bool ValidateValueType(object value, Type type)
{
if (value == null)
{
if (!type.IsValueType)
return true;
return (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>));
}
return type.IsAssignableFrom(value.GetType());
}
public override object GetPropertyValue(string propertyName)
{
object customPropertyValue;
if (_customPropertyValues.TryGetValue(propertyName, out customPropertyValue))
return customPropertyValue ?? _customProperties.First(p => p.Name == propertyName).GetDefaultValue(this);
throw new Exception("There is no property " + propertyName);
}
public override void SetPropertyValue(string propertyName, object value)
{
CustomPropertyInfo propertyInfo = _customProperties.FirstOrDefault(prop => prop.Name == propertyName);
object customPropertyValue;
if (!_customPropertyValues.TryGetValue(propertyName, out customPropertyValue))
throw new Exception("There is no property " + propertyName);
if (ValidateValueType(value, propertyInfo.PropertyType))
{
if (customPropertyValue != value)
{
_customPropertyValues[propertyName] = value;
OnPropertyChanged(propertyName);
}
}
else throw new Exception("Value is of the wrong type or null for a non-nullable type.");
}
public PropertyInfo[] GetProperties()
{
return this.GetCustomType().GetProperties();
}
public Type GetCustomType()
{
return _customtype ?? (_customtype = new CustomType(typeof(T)));
}
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
foreach (var dependantCustomPropertyInfo in _customProperties.OfType<IDependantCustomPropertyInfo>().Where(dcpi => dcpi.Properties.Contains(propertyName)))
PropertyChanged(this, new PropertyChangedEventArgs(dependantCustomPropertyInfo.Name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private class CustomType : Type
{
Type _baseType;
public CustomType(Type delegatingType)
{
_baseType = delegatingType;
}
public override Assembly Assembly
{
get { return _baseType.Assembly; }
}
public override string AssemblyQualifiedName
{
get { return _baseType.AssemblyQualifiedName; }
}
public override Type BaseType
{
get { return _baseType.BaseType; }
}
public override string FullName
{
get { return _baseType.FullName; }
}
public override Guid GUID
{
get { return _baseType.GUID; }
}
protected override TypeAttributes GetAttributeFlagsImpl()
{
throw new NotImplementedException();
}
protected override ConstructorInfo GetConstructorImpl(BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers)
{
throw new NotImplementedException();
}
public override ConstructorInfo[] GetConstructors(BindingFlags bindingAttr)
{
return _baseType.GetConstructors(bindingAttr);
}
public override Type GetElementType()
{
return _baseType.GetElementType();
}
public override EventInfo GetEvent(string name, BindingFlags bindingAttr)
{
return _baseType.GetEvent(name, bindingAttr);
}
public override EventInfo[] GetEvents(BindingFlags bindingAttr)
{
return _baseType.GetEvents(bindingAttr);
}
public override FieldInfo GetField(string name, BindingFlags bindingAttr)
{
return _baseType.GetField(name, bindingAttr);
}
public override FieldInfo[] GetFields(BindingFlags bindingAttr)
{
return _baseType.GetFields(bindingAttr);
}
public override Type GetInterface(string name, bool ignoreCase)
{
return _baseType.GetInterface(name, ignoreCase);
}
public override Type[] GetInterfaces()
{
return _baseType.GetInterfaces();
}
public override MemberInfo[] GetMembers(BindingFlags bindingAttr)
{
return _baseType.GetMembers(bindingAttr);
}
protected override MethodInfo GetMethodImpl(string name, BindingFlags bindingAttr, Binder binder, CallingConventions callConvention, Type[] types, ParameterModifier[] modifiers)
{
throw new NotImplementedException();
}
public override MethodInfo[] GetMethods(BindingFlags bindingAttr)
{
return _baseType.GetMethods(bindingAttr);
}
public override Type GetNestedType(string name, BindingFlags bindingAttr)
{
return _baseType.GetNestedType(name, bindingAttr);
}
public override Type[] GetNestedTypes(BindingFlags bindingAttr)
{
return _baseType.GetNestedTypes(bindingAttr);
}
public override PropertyInfo[] GetProperties(BindingFlags bindingAttr)
{
PropertyInfo[] clrProperties = _baseType.GetProperties(bindingAttr);
if (clrProperties != null)
return clrProperties.Concat(_customProperties).ToArray();
return _customProperties.ToArray();
}
protected override PropertyInfo GetPropertyImpl(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers)
{
return GetProperties(bindingAttr).FirstOrDefault(prop => prop.Name == name) ?? _customProperties.FirstOrDefault(prop => prop.Name == name);
}
protected override bool HasElementTypeImpl()
{
throw new NotImplementedException();
}
public override object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, ParameterModifier[] modifiers, System.Globalization.CultureInfo culture, string[] namedParameters)
{
return _baseType.InvokeMember(name, invokeAttr, binder, target, args, modifiers, culture, namedParameters);
}
protected override bool IsArrayImpl()
{
throw new NotImplementedException();
}
protected override bool IsByRefImpl()
{
throw new NotImplementedException();
}
protected override bool IsCOMObjectImpl()
{
throw new NotImplementedException();
}
protected override bool IsPointerImpl()
{
throw new NotImplementedException();
}
protected override bool IsPrimitiveImpl()
{
return _baseType.IsPrimitive;
}
public override Module Module
{
get { return _baseType.Module; }
}
public override string Namespace
{
get { return _baseType.Namespace; }
}
public override Type UnderlyingSystemType
{
get { return _baseType.UnderlyingSystemType; }
}
public override object[] GetCustomAttributes(Type attributeType, bool inherit)
{
return _baseType.GetCustomAttributes(attributeType, inherit);
}
public override object[] GetCustomAttributes(bool inherit)
{
return _baseType.GetCustomAttributes(inherit);
}
public override bool IsDefined(Type attributeType, bool inherit)
{
return _baseType.IsDefined(attributeType, inherit);
}
public override string Name
{
get { return _baseType.Name; }
}
}
private class CustomPropertyInfo : PropertyInfo
{
private string _name;
private Type _type;
private object _defaultValue;
private List<Attribute> _attributes;
public CustomPropertyInfo(string name, Type type, object defaultValue = null, List<Attribute> attributes = null)
: this(name, type, attributes)
{
_defaultValue = defaultValue;
}
protected CustomPropertyInfo(string name, Type type, List<Attribute> attributes = null)
{
_name = name;
_type = type;
_attributes = attributes;
}
public virtual object GetDefaultValue(DynamicBaseType entity)
{
return _defaultValue;
}
public override PropertyAttributes Attributes
{
get { throw new NotImplementedException(); }
}
public override bool CanRead
{
get { return true; }
}
public override bool CanWrite
{
get { return true; }
}
public override MethodInfo[] GetAccessors(bool nonPublic)
{
throw new NotImplementedException();
}
public override MethodInfo GetGetMethod(bool nonPublic)
{
throw new NotImplementedException();
}
public override ParameterInfo[] GetIndexParameters()
{
throw new NotImplementedException();
}
public override MethodInfo GetSetMethod(bool nonPublic)
{
throw new NotImplementedException();
}
public override object GetValue(object obj, BindingFlags invokeAttr, Binder binder, object[] index, System.Globalization.CultureInfo culture)
{
return ((DynamicBaseType)obj).GetPropertyValue(_name);
}
public override void SetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, object[] index, System.Globalization.CultureInfo culture)
{
((DynamicBaseType)obj).SetPropertyValue(_name, value);
}
public override Type PropertyType
{
get { return _type; }
}
public override Type DeclaringType
{
get { throw new NotImplementedException(); }
}
public override object[] GetCustomAttributes(Type attributeType, bool inherit)
{
return _attributes == null ? new object[0] : _attributes.Where(a => a.GetType() == attributeType).ToArray();
}
public override object[] GetCustomAttributes(bool inherit)
{
return _attributes == null ? new object[0] : _attributes.ToArray();
}
public override bool IsDefined(Type attributeType, bool inherit)
{
throw new NotImplementedException();
}
public override string Name
{
get { return _name; }
}
public override Type ReflectedType
{
get { throw new NotImplementedException(); }
}
}
private interface IDependantCustomPropertyInfo
{
string Name { get; }
string[] Properties { get; }
}
private class CustomPropertyInfo<V> : CustomPropertyInfo, IDependantCustomPropertyInfo
{
private Func<T, V> _get;
private Action<T, V> _set;
private string[] _properties;
public CustomPropertyInfo(string name, Func<T, V> get, Action<T, V> set = null, List<Attribute> attributes = null, string[] properties = null)
: base(name, typeof(V), attributes)
{
_get = get;
_set = set;
_properties = properties;
}
public string[] Properties
{
get { return _properties; }
}
public override object GetDefaultValue(DynamicBaseType entity)
{
return _get((T)entity);
}
public override bool CanWrite
{
get { return _set != null; }
}
public override object GetValue(object obj, BindingFlags invokeAttr, Binder binder, object[] index, System.Globalization.CultureInfo culture)
{
return _get((T)obj);
}
public override void SetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, object[] index, System.Globalization.CultureInfo culture)
{
if (_set == null)
throw new InvalidOperationException();
_set((T)obj, (V)value);
}
}
}
Now to use it it’s very easy.
This is my class Customer:
public class Customer : DynamicBaseType<Customer>
{
private String firstName;
public String FirstName
{
get { return firstName; }
set
{
firstName = value;
OnPropertyChanged("FirstName");
}
}
private String lastName;
public String LastName
{
get { return lastName; }
set
{
lastName = value;
OnPropertyChanged("LastName");
}
}
}
And I use it like this in my ViewModel:
Customer.AddProperty("Age", typeof(int));
Customer.AddProperty("Married", typeof(bool));
Customer.AddProperty("FullName", c => string.Format("{0} {1}", c.LastName, c.FirstName), properties:new string[] { "LastName", "FirstName" });
customers[0].SetPropertyValue("Age", 40);
customers[0].SetPropertyValue("Married", true);
customers[1].SetPropertyValue("Age", 45);
customers[1].SetPropertyValue("Married", true);
What is interesting here comparing to her solution is the fact that FullName is calculated from other properties. And if I change the LastName or the FirstName of a Customer, my Customer instance will also raise a PropertyChanged with FullName.