SL5: ICustomTypeProvider

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.

This entry was posted in 13461. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>