Custom type providers in Silverlight 5

Ok, after yesterdays less technical post, I guess it is time to get back to writing a real techy post. This time, it is even techy, or geeky, enough to be about Silverlight 5, which currently is still in beta.

This obviously means that we can’t use it, but it doesn’t mean that we can’t have a look at some of the new features and start thinking about how we can upgrade our existing applications as soon as the new version is released…

Custom type providers might sound really obscure, and to be honest, not very interesting. But that’s where you are wrong…

Custom type providers can be used for a lot of things. They basically give us the ability to play with the data binding features in Silverlight by manually, or automagically, define and modify the interface of our view models at runtime.

And why is this a good thing? Well…a lot of systems use metadata based information to make the system flexible. A good example is Microsoft CRM who stores everything as entities with user defined attributes. This wreaks havoc when you try to present it in Silverlight using data binding. You can obviously do it with some hacks, or by modifying the VMs and recompiling every time the entities change. But with custom type providers, this is quite easily fixed.

So how does it work? Well, when the a control is data bound, the data binding engine uses reflection to locate the property that is bound, which basically means querying a Type object retrieved from the VMs. In Silverlight 5 however, the data binding engine has been modified to look for a new interface called ICustomTypeProvider before getting the Type object from the VM. And if it finds this new interface, it uses that interface’s GetCustomType() method to retrieve the Type to use instead of the default GetType().

This has been enabled by opening up certain types in the System.Reflection namespace for inheritance. These were previously locked down as inheriting them were very rarely required, if ever…

This is pretty sweet, as it let’s us create our own Type implementation, which is actually quite simple in the VM situation as we really only care about properties. So how do we go about doing this?

I have of course created a sample to show off a simple implementation. It consists of a single Silverlight 5 application. It doesn’t even contain a hosting website…

In the application, I have defined a “service” that will return dynamic models

public interface IDynamicObjectProvider
{
IList<IDynamicObject> GetDynamicObjects();
}

public interface IDynamicObject
{
string Name { get; }
Dictionary<string, object> Metadata { get; }
}

The actual implementation is just a hardcoded moron sample, but it works and is not the important part

public class DefaultDynamicObjectProvider : IDynamicObjectProvider
{
public IList<IDynamicObject> GetDynamicObjects()
{
var objects = new List<IDynamicObject>();

var metaData = new Dictionary<string, object>();
metaData.Add("Count", 4);
metaData.Add("Color", Colors.Magenta);
var obj = new DynamicObject("Object 1", metaData);
objects.Add(obj);

metaData = new Dictionary<string, object>();
metaData.Add("Count", 1);
metaData.Add("Color", Colors.Yellow);
obj = new DynamicObject("Object 2", metaData);
objects.Add(obj);

return objects;
}

private class DynamicObject : IDynamicObject
{
public DynamicObject(string name, Dictionary<string, object> metadata)
{
Name = name;
Metadata = metadata;
}

public string Name { get; private set; }
public Dictionary<string, object> Metadata { get; private set; }
}
}

The service is injected into the main VM during application startup, and is then used to get the dynamic objects to display

public class MainViewModel
{
public MainViewModel(IDynamicObjectProvider objectProvider)
{
Objects = objectProvider.GetDynamicObjects().Select(o => new DynamicObjectViewModel(o)).ToList();
}

public IList<DynamicObjectViewModel> Objects { get; private set; }
}

As you can see, the IDynamicObjects that are returned from the GetDynamicObjects() call are wrapped in another VM called DynamicObjectViewModel and then placed in an IList<> that is used as the ItemsSource for a DataGrid control like this

<Grid x:Name="LayoutRoot" Background="White">
<sdk:DataGrid ItemsSource="{Binding Objects}" />
</Grid>

So where is the custom type provider? Well, as you might have figured out, the class that benefits from having its interface rewritten is the DynamicObjectViewModel. Without modifying the interface for that class, we would only be able to bind to the dynamic object’s Name and Metadata properties. At least if we do not go and pull out the values from the Metadata array and put them in properties on the VM. But this would put us in a world of hurt if the metadata ever changed. So this is where the custom type provider comes into play…

The way we crate a custom type provider is by implementing the interface called ICustomTypeProvider. The interface only declares a single method, GetCustomType(), which returns an instance of Type.

In this case, I return an instance of a class I have created called DynamicObjectType<T>, which inherits from Type. The implementation is not that complicated as we, as mentioned before, only need to support properties. There are still a lot of properties and methods to override from the abstract base class. But most of them can be ignored, or implemented by passing the request to the type defined for T.

As there are a LOT of members to implement, I will not show it here. But the full implementation is available in the download below. The only members that are interesting are  the constructor and GetProperties() and GetPropertyImpl() ones.

The constructor takes an IEnumerable of type DynamicObjectPropertyInfo as a parameter. I will get back to the propertyinfo class, but for now, all you need to know is that it each object defines a specific property.

The GetProperties() method is responsible for returning an array of PropertyInfo objects, defining all the properties available for the type. In this case, it checks for any property on the type T that isn’t named “Metadata”, and combines those properties with the list of properties passed into the constructor.

The GetPropertyImpl() on the other hand is responsible for returning a specific PropertyInfo for a property with a specified name on the defined type. In this case, it once again starts out by checking the “real” implementation for a corresponding property, and if that isn’t found, it checks the “dynamic” property list.

public class DynamicObjectType<T> : Type
{
List<DynamicObjectPropertyInfo> _dynamicProperties = new List<DynamicObjectPropertyInfo>();

public DynamicObjectType(IEnumerable<DynamicObjectPropertyInfo> props)
{
_dynamicProperties.AddRange(props);
}

public override PropertyInfo[] GetProperties(BindingFlags bindingAttr)
{
var clrProperties = typeof(T).GetProperties(bindingAttr).Where(p => p.Name != "Metadata");
if (clrProperties != null)
{
return clrProperties.Concat(_dynamicProperties).ToArray();
}
else
return _dynamicProperties.ToArray();
}
protected override PropertyInfo GetPropertyImpl(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers)
{
PropertyInfo propertyInfo = typeof(T).GetProperties(bindingAttr).Where(p => p.Name == name).FirstOrDefault();
if (propertyInfo == null)
{
propertyInfo = _dynamicProperties.Where(p => p.Name == name).FirstOrDefault();
}
return propertyInfo;
}
...
}

So, the implementation of GetCustomType() looks like this

public Type GetCustomType()
{
var props = new List<DynamicObjectPropertyInfo>();
foreach (var key in Object.Metadata.Keys)
{
props.Add(new DynamicObjectPropertyInfo(key, Object.Metadata[key].GetType(), SetValue, GetValue));
}
return new DynamicObjectType<IDynamicObject>(props);
}

What isn’t a hundred percent evident from the snippet above is that the SetValue and GetValue parameters are actually delegates (Action<string, object> and Func<string, object>), which look like this

private object GetValue(string key)
{
if (!Object.Metadata.ContainsKey(key))
return null;

return Object.Metadata[key];
}
private void SetValue(string key, object value)
{
if (!Object.Metadata.ContainsKey(key))
Object.Metadata.Add(key, value);
else
Object.Metadata[key] = value;
}

Ok, this is getting complicated, I know… Sorry!" But if you look at the code in the download below I am pretty sure it will make sense…

There is just one last thing to have a look at, and that is the implementation of the DynamicObjectPropertyInfo class. It is a fairly simple class, but it gets quite large due to all the abstract methods in the base class PropertyInfo.

Luckily, once again, we can ignore most of those overrides and focus on only a handful of them. First we have the Name, PropertyType, CanRead and CanWrite properties. These return exactly what you would expect. So I return the name and type passed in to the constructor, as well as “true” for both of the “Can-properties”.

The two GetCustomAttributes() methods have to have an implementation as they get called, but the implementation can be as stupid as just returning null.

The only 2 methods we really care about is the GetValue() and SetValue(), but as you might have figured out from the code above, this functionality is delegated to those delegates that we pass into the constructor. So the implementation actually turns into something as simple as this

public override object GetValue(object obj, BindingFlags invokeAttr, Binder binder, object[] index, System.Globalization.CultureInfo culture)
{
return _getter(_name);
}
public override void SetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, object[] index, System.Globalization.CultureInfo culture)
{
_setter(_name, value);
}

Where _getter and _setter are local variables holding the delegates passed to the class.

That’s actually all there is to is. It might seem like a lot, but it really isn’t. It can be simplified a whole lot if you make the implementation less re-usable. And probably made a whole lot more complicated if you made it smarter and more generic… But I found this to be good enough, and simple enough, for the sample…

The end result? Well, it isn’t impressive, but it looks like this

image

So all of this work and code has created this less than impressive screen…? Yes… But if you have ever worked on a dynamic system, you know how hard this is to achieve in a flexible way with data binding. We have managed to modify the interface of the VM at runtime and through this get the DataGrid to generate columns and bind up values from properties that doesn’t actually exist on the VM.

If I change the DefaultDynamicObjectProvider to return a new metadata value, the DataGrid automatically just adapts to look like this

image

without modifying the VM at all… And yes, updating the values work fine!

That’s it for this time. I hope you have some use of the above information! At least when Silverlight 5 is released…

Code is available here: DarksideCookie.CustomTypeProviderDemo.zip (17.17 kb)

Cheers!

Comments (2) -

Very impressive! If only SL4 had this a year ago then it would've saved me allot of time!

Like you blog btw Smile

I tried your code and failed with using it on DevExpress v11.2 GridControl, please help,  does it work with other Grid controls ?

Comments are closed