In our project we support custom schema on our tables. We support both extending the domain tables and allow the creation of completely ad hoc tables. Currently in the project to display a list of these records we use the custom schema framework which returns a wrapper object and usually then build a datatable on the client side out of the results and bind a grid to that. Recently I discover ICustomTypeDescriptor which allows an object to provide its own type information.
The default databinding implementation uses reflection to get the properties and values for those properties from an object. The ICustomTypeDescriptor interface allows an object to step in and provide this information dynamically.
So say for example I have a custom “ad hoc” table (I use quotes here because in the case of this post its actually just the Employee table from the Chinook database, but we’ll suspend disbelief and say its ad hoc.
I then have a custom field schema implementation, again for the sake of the post I have a very naïve implementation that just reads a datareader into a dictionary. Obviously a real implementation would probably need metadata, etc., for the post I’m keeping it simple.
So I run a simple select statement and end up with a list of CustomSchemaTableRecord objects:
List<CustomSchemaTableRecord> records = new List<CustomSchemaTableRecord>();
using (SqlConnection connection = new SqlConnection(Properties.Settings.Default.ConnectionString))
{
SqlCommand command = new SqlCommand(
"SELECT * FROM Employee",
connection);
connection.Open();
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
records.Add(new CustomSchemaTableRecord(reader));
}
reader.Close();
}
dataGridView1.DataSource = records;
Which as you can see really is just a dictionary internally (again naïve implementation for simplicity):

However when I databind these records to the datagrid in the last line I get:

Nothing…great post right?
Why do we get nothing? CustomSchemaTableRecord has no properties. In fact it has basically no external interface at all. All it has is a constructor and a private dictionary to store its values in. No external interface means nothing returned to reflection and so nothing to databind to.
But, if we implement ICustomTypeDescriptor on CustomSchemaTableRecord (this is snipped to just the interesting parts, see the link above to the MSDN documentation to see all the required methods):
#region ICustomTypeDescriptor Members
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
List<PropertyDescriptor> properties = new List<PropertyDescriptor>();
foreach (var kvp in values)
{
properties.Add(new CustomSchemaTypePropertyDescriptor(kvp.Key, kvp.Value.GetType()));
}
return new PropertyDescriptorCollection(properties.ToArray());
}
Note: This code is not checking for nulls, etc. its probably not safe to copy and paste directly…
Here I return a PropertyDescriptorCollection. PropertyDescriptor is an abstract class that I implemented with my CustomSchemaTypePropertyDescriptor class (again snipped for brevity, see link above etc., etc.):
class CustomSchemaTypePropertyDescriptor : PropertyDescriptor
{
private Type type;
public CustomSchemaTypePropertyDescriptor(string key, Type type)
:base(key,null)
{
this.type = type;
}
public override object GetValue(object component)
{
CustomSchemaTableRecord record = component as CustomSchemaTableRecord;
return record.GetValueForKey(this.Name);
}
Again here null checks, etc…
For the keen eyed, you’ll notice when GetValue is called I am calling the GetValueForKey method on my CustomSchemaTableRecord, which is simply this:
public object GetValueForKey(string key)
{
return values[key];
}
Now that I have implemented ICustomTypeDescriptor to provide my list of dynamic properties and created my custom PropertyDescriptor to provide the value back to the databinding when asked, when we rerun the app we now get:

Since this implementation just works against my ad hoc custom field framework, I can simply change my selection criteria to say “SELECT * FROM Customer” and I get:

Perfect databinding for an table with dynamic schema.
A little snag I ran into along the way was that databinding (or maybe just the grid itself) seems to be picky about the list of items, it must be a list that implements ITypedList. At first I was just using an array and it did not work. However once I switched to a generic List<T>, all was right.
In our project we seldom databind directly to the domain objects that support custom schema. However, this new implementation will decrease the amount of code in the ad hoc table implementation. No need any longer to build ugly in memory datatables to databind to. Just take the custom schema records that are returned out of the business logic and implement ICustomTypeDescriptor directly on them, or write a wrapper to implement it and hand them off as the datasource, that simple.