Custom collections and databinding.

I always prefer custom business objects to .Net Datasets and Datatables when I write data representation logic for my web apps. So data abstraction layers I write are plenty of classes representing business entities and type-safe custom collections for entities enlisting. While collecting instances in a custom collection is not an hard task at all, provide to a collection functionalities like complex databinding, sorting and filtering is a little bit harder. Furthermore incapsulating such as capibilities in only one generic class requires a bit of time… But now I got it! I have this generic class (I call it CollectionView) that:

  • accepts as source any class implementing ICollection interface
  • generates a set of bindable columns based on the Type of the objects collected by the source.
  • allows the manipulation of the columns set (reordering, visibility, etc)
  • supports sorting and filtering
  • is a valid datasource for .Net WebControls

Let’s take a look to how I did this. First of all class signature and construtors:

public class CollectionView : ITypedList, IEnumerable, ICollection { public CollectionView(ICollection collection) { m_collection = collection; } public CollectionView(ICollection collection, Type collectionItemsType) { m_collection = collection; m_collectionItemsType = collectionItemsType; } // }

CollectionView implements 3 interfaces: ITypedList, IEnumerable, and ICollection. IEnumerable is obviously required for a complex databinding and ICollection (through Count property) allows operation such as paging. ITypedList allows to notify the dataconsumer (the web control that CollectionView will be bound to) which are the properties of the source collection items we want to be considered in the binding process. To be more precise: during the databinding process the dataconsumer invokes the GetItemProperties() method on ITypeList and receive the collection of property descriptors that it will use to bind it’s items (i.e: DataGrid items) to the collection items. Here ITypedList declaration:

public interface ITypedList { PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors); string GetListName(PropertyDescriptor[] listAccessors); }

Looking at the constructors you can understand that as the source collection is referenced through ICollection is possible to specify as source almost all the kind of collection you can have in the Framework. Optionally, also the Type of objects the source contains can be explicitly declared. This optional declaration is important because influences the way CollectionView builds the set of bindable columns and in turn this columns set can influence the PropertyDescriptorCollection instance returned by the GetItemProperties() method we’ve just seen. It will soon be more clear as I’m going to explain it. The return value of GetItemProperties() could have been exactly the result of a query for the public properties exposed by items in the source collection. But I decided to add the possibility, for the caller of CollectionView, to reorder these properties or to hide some of them. So what I’ve done is:

  • I created a class named CollectionViewColumnSet that is a type-safe collection (another one!) of CollectionViewColumn objects and that is exposed by CollectionView through the Columns property.
  • This column set is generated (the first time the Columns property is accessed) querying the public properties of the items in the source collection and every CollectionViewColumn will map one of these properties.
  • CollectionView callers are able to reorder the columns or hide some of them through properties such as Visible (bool) and Ordinal (double) exposed by CollectionViewColumn class.
  • When the dataconsumer calls GetItemProperties() the PropertyDescriptorCollection to be returned is build reflecting the state of each column in the column set.

Here the 2 routines I wrote to query the properties of the items in the source collection (DiscoverPropertyDescriptors) and to build the column set (EmitColumnsFromProperties):

private PropertyDescriptorCollection DiscoverPropertyDescriptors() { if (m_collectionItemsType != null) return TypeDescriptor.GetProperties(m_collectionItemsType); else { // if caller has not explict declared the Type of the items in the source collection // I try to discover if by my self: object item = GetAnyItemFromTheSourceCollection(); if (item != null) return TypeDescriptor.GetProperties(item); else { // in this case the dataconsumer will not be able to complete the binding process // but I can’t do anything abount that. May be I should throw an exception… PropertyDescriptor[] properties = new PropertyDescriptor[0]; return new PropertyDescriptorCollection(properties); } } } private CollectionViewColumnSet EmitColumnsFromProperties() { CollectionViewColumnSet cols = new CollectionViewColumnSet(); PropertyDescriptorCollection descriptors = DiscoverPropertyDescriptors(); foreach (PropertyDescriptor propertyDesc in descriptors) { CollectionViewColumn col = new CollectionViewColumn(propertyDesc.Name, propertyDesc); col.Ordinal = cols.Count; cols.Add(col); } return cols; }

Now you can understand what I meant when I was saying that the optional explicit declaration (with the second overload of the constructor) of the Type of the items in the source collection is important: in the case in which the Type is declared I call TypeDescriptor.GetProperties() passing it as argument, in the second case I have to try to find an item in the collection and use this as argument. Is important to know that: taken an instance of any class fooInstance the results of TypeDescriptor.GetProperties(fooInstance) and TypeDescriptor.GetProperties(fooInstance.GetType()) can be different. In fact, and I report what the Framework documentation says, the properties for a component can differ from the properties of a class, because the site can add or remove properties if the component is sited.

To complete the column set life-cycle, this is the implementation of ITypedList.GetItemProperties() method in which I reflect the column set state to the properties returned to the dataconsumer:

public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors) { return ReflectPropertiesFromColumns(); } private PropertyDescriptorCollection ReflectPropertiesFromColumns() { ArrayList descriptors = new ArrayList(); foreach (CollectionViewColumn col in this.Columns) { if (col.Visible) descriptors.Add(col.PropertyDescriptor); } PropertyDescriptor[] properties = new PropertyDescriptor[descriptors.Count]; descriptors.CopyTo(properties, 0); return new PropertyDescriptorCollection(properties); }

I have another couple of interesting things to tell about managing the properties-columns and about filtering the source collection, but it’s 4.00 AM so: to be continued…


~ by Matteo on November 5, 2005.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: