Introduction
There is a nice underused feature in .NET framework / ASP.NET � the TypeDescriptors mechanism. The mechanism resides in the System.ComponentModel
namespace, and is generally represented by the TypeDescriptor
, CustomTypeDescriptor
classes, a ICustomTypeDescriptor
interface, and some other supporting classes. As we can see by using the brilliant Lutz Roeder�s Reflector, these classes are mostly used by some controls (including the brand new AJAX Toolkit) and DataGrid
s / DataBinder
.
Shortly said, this class allow us to design a type, a datasource, that when bound to a .NET control, for example, via the convenient <%# %>
ASP.NET syntax, does not lead to reflection calls. This is done by providing property information dynamically, at runtime. Here�s a quick how to, with a real world scenario.
Details
In our project, we had some legacy code that returned data in some form of object[][]
(actually, jagged array) which contained some metadata (columns, length, availability of next data pages, etc.). We needed to easily bind it in ASPX pages. That quickly led us to ugly things like <%# ((object[])DataItem)[3][4]%>
, or something like that.
What I wanted to do was to have a custom class containing tabular data (columns and values for each row) + some custom metadata. I also wanted that class to be bindable the same way as a DataTable
does, i.e.: <%# Eval(�ColumnName�) %>
.
First of all, I thought that designing a simple class with an indexed property would be enough, but that led us to: <%# Eval(�[ColumnName]�) %>
. Without unneeded brackets that didn�t work. That was an �OK� solution, but I was still curios: if a DataTable
can do that type of thing, I should be able to do it too.
So, again, I took the reflector and went to the DataBinder
class where I quickly found the following code (the GetPropertyValue
method):
PropertyDescriptor descriptor1 =
TypeDescriptor.GetProperties(container).Find(propName, true);
return descriptor1.GetValue(container);
Woa! That�s not just the easy reflection way, as I always thought. Actually, as further investigation revealed, TypeDecriptor
when not provided with an implementation of the ICustomTypeDescriptor
interface just reverts to the plain old reflection way. So, the other part was easy -> read MSDN, see how DataTable
implements ICustomTypeDescriptor
, and write my own implementation for it:
public class DataRetriverResultRow: CustomTypeDescriptor
{
private DataRetriverResultSet proxy;
public override PropertyDescriptorCollection GetProperties()
{
return proxy.GetPropertyDescriptorCollection(null);
}
}
In the �DataTable
� class, it's the place where actual property information is created (because all "DataTable
" rows have the same properties - one for each column):
internal PropertyDescriptorCollection
GetPropertyDescriptorCollection(Attribute[] attributes)
{
if (this.propertyDescriptorCollectionCache == null)
{
int colCount = this.columns.Length;
PropertyDescriptor[] descriptors = new PropertyDescriptor[colCount];
for (int i = 0; i < colCount; i++)
{
descriptors[i] = new DataRetriverResultRowPropertyDescriptor(i,
this.columns[i]);
}
this.propertyDescriptorCollectionCache =
new PropertyDescriptorCollection(descriptors);
}
return this.propertyDescriptorCollectionCache;
}
And finally, the PropertyDescriptor
(which is the class that actually describes a property and knows how to get a value from it). Treat it as a custom variant of the PropertyInfo
class:
private class DataRetriverResultRowPropertyDescriptor : PropertyDescriptor
{
int columnIndex;
public DataRetriverResultRowPropertyDescriptor(int index,
string name)
: base(name, null)
{
this.columnIndex = index;
}
public override Type ComponentType
{
get { return typeof(DataRetriverResultRow); }
}
public override object GetValue(object component)
{
return ((DataRetriverResultRow)
component).GetValue(columnIndex);
}
}
And that�s all. Basically, we just created some kind of meta descriptor which generates property info at runtime, using dynamic data (in this case, the number and order of columns in the parent �data table�).
Now, when DataBinder.Eval
gets the data row of our object, it operates with a custom created and cached PropertyDescriptorCollection
and uses our GetValue
method, instead of PropertyInfo.GetValue
. That's definitely a perfomance bonus. Also, we get a nifty syntax of databinding without a bit odd ["here goes a column name"].