Introduction
The GridView control in ASP.NET 2.0 is a very robust control and I particularly like how we can map GridView controls to object data sources. Furthermore, I appreciate how the GridView enables sorting and paging with no code required. Very nice! A benefit of mapping GridViews to object data sources (in my opinion) is that it promotes a tiered architecture. However, if you map a GridView to an object data source you may encounter the following exception when trying to sort a GridView:
The data source 'objDataSource' does not support sorting with IEnumerable data. Automatic sorting is only supported with DataView, DataTable, and DataSet
Now, it seems a bit absurd to me to lose all the great benefits of encapsulating data within middle tier objects by opting to map GridViews directly to datasets and not the pertinent application objects. All of this compromise just so sorting can work within the GridView. In this brief article I want to mention two techniques that I use to allow for mapping GridViews to DataTables that are built upon existing middle tier objects. I tend to think of this as a pseudo adapter pattern that converts my model objects into DataTables. A benefit of this is that I can leverage all my previously written middle tier logic while also realizing the great features of the GridView control like sorting. The two techniques are:
1. Using reflection to initialize middle tier “model” layer objects.
2. Using reflection to convert middle tier “model” layer objects into DataTables.
Prior to getting into the code, I should mention the layers I typically have in my application. This will make it easier to conceptualize where I employ the techniques above.
1. Model Layer – This layer holds objects that encapsulate data. This layer consists of all lightweight serializable classes that declare properties only. No methods whatsoever in this layer. These are the classes that I use to pass data through the various layers of the application.
2. Controller Layer – This layer is the glue between my ASP.NET application and the Business Logic Layer.
3. Business Logic Layer – The Business Logic Layer acts on the data within the model layer. This layer also interfaces with the data logic layer by “asking” the data layer to perform CRUD (Create, Retrieve, Update, and Delete) operations on ModelLayer objects.
4. Data Logic Layer – This layer is used to perform the actual CRUD operations on the ModelLayer objects.
The first technique that I use is within the Data Logic Layer. Once I retrieve a DataSet from a database I create and initialize the pertinent Model Layer class using reflection. In essence this method iterates through all the columns of a row in a dataset and invokes the pertinent set methods within my model object. The invocation occurs only when my model object has property names the match the column names within the data set. The result of this method is an initialized Model Layer object.
Public Function InitializeFromDataRow(ByVal aDataRow As System.Data.DataRow, ByVal aType As System.Type) As Object
Implements IDLL.IBaseDataObject.InitializeFromDataRow
Try
Dim props As PropertyInfo()
Dim myObject As Object
' Create our object through reflection
myObject = Activator.CreateInstance(aType)
Dim p As PropertyInfo
props = aType.GetProperties()
'Now go set all the public attributes based on the columns and values within the datatable
For Each p In props
Dim params(0) As Object
'Make sure we have a column in dataset for the attribute we are trying to set.
If aDataRow.Table.Columns.Contains(p.Name) = True Then
params(0) = aDataRow.Item(p.Name)
p.GetSetMethod.Invoke(myObject, params)
End If
Next
Return myObject
Catch ex As Exception
Throw ex
End Try
Return Nothing
End Function
The second technique that I use is in the Controller Layer. I include a class within the Controller Layer asks the Business Logic Layer to convert a Model Layer object into a DataTable. This enables me to map my GridView to my model objects in a round about way. I say a round about way because all I am really doing is taking a Model Layer object and converting it to a DataTable. This is where the adapter like pattern comes into play.
Public Function ConvertToDataTable(ByVal list As System.Collections.IList, ByVal aType As System.Type) As DataTable
Try
Dim aDataTable As New DataTable
Dim props As PropertyInfo()
Dim p As PropertyInfo
props = aType.GetProperties()
Dim anObject As Object
'Create all the pertinent columns
For Each p In props
aDataTable.Columns.Add(New DataColumn(p.Name, p.PropertyType))
Next
'Now go add all the data into datatable.
For Each anObject In list
Dim row(props.Length - 1) As Object
Dim i As Integer
i = 0
row(props.Length - 1) = New Object()
For Each p In props
row(i) = p.GetValue(anObject, Nothing)
i = i + 1
Next
aDataTable.Rows.Add(row)
Next
Return aDataTable
Catch ex As Exception
Throw ex
End Try
Return Nothing
End Function
Summary
What makes this possible is reflection. I have attached a very simple web application that makes use of the two techniques discussed above. Note, in the attached source code the first grid results in an exception upon sorting while the second grid works like a charm. The key line of code in the example that circumvents the exception is:
Return myObject.ConvertToDataTable(myEmployees, GetType(IModelLayer.IEmployee))
This returns a DataTable representation of an Employee Model object that my GridView can play nicely with.
Brian Rush