NOTE: This article is one of a three-part series based on the same code. As such, some of the code in the referenced download is not specifically targeted at this topic. The other two topics are Declarative Databinding of Nested Object Properties to GridView Columns and Dynamic Rendering of Images in ASP.NET.

Another NOTE:  The code provided has been written to use the AdventureWorks database for SQL 2005, which can be found on Codeplex here.

The GridView is arguably one of the most powerful and widely-used controls in the ASP.NET developer's  toolbox.  Particularly in the realm of business software, it would be difficult to find an application that doesn't contain at least one grid of data somewhere.  Combine  the GridView with the ObjectDataSource control, and you have a very powerful combination that allows for good architectural separation of presentation layer code from the middle-tier.

Providing methods in middle-tier objects that return collections allows the UI developer to easily create an ObjectDataSource and bind it to a GridView with very little or no code.  Let's look as some simplified code to demonstrate this.  Let's assume we have a Product class, which has properties for Name, ProductNumber, and ListPrice, as well as a ProductCollection class with a method called GetAllProducts, which returns a List<Product>.  To create a simple gridview which displays this data, we could use something like this,  which uses BoundField columns to retrieve the data from the properties of the objects on the collection returned from ProductCollection.GetAllProducts().

<asp:ObjectDataSource  
 ID="ods_allProducts" 
 runat="server" 
 SelectMethod="GetAllProducts" 
 TypeName="Aptera.BlogSamples.AdventureWorks.Production.ProductCollection" >
</asp:ObjectDataSource>

 

<asp:GridView 
 ID="gv_products" 
 runat="server" 
 AutoGenerateColumns="False" 
 DataSourceID="ods_allProducts">
  <Columns>
   <asp:BoundField
    DataField="Name"
    HeaderText="Name" />
   <asp:BoundField
    DataField="ProductNumber"
    HeaderText="ProductNumber" />
   <asp:BoundField
    DataField="ListPrice"
    HeaderText="ListPrice"  />
  </Columns>
</asp:GridView>

This is nice, but what if our Product class contains other objects, with their own properties which we would like to also display on our GridView?  For example, what if our Product class contains a ProductSubcategory object, which in turn has a property called Name?  How can we display this subcategory name on our GridView?  At first glance, it seems like we should be able to do something lie the following, and simply bind directly to the nested property:

<asp:GridView 
 ID="gv_products"
 runat="server"
 AutoGenerateColumns="False"
 DataSourceID="ods_allProducts">
 <Columns>
  <asp:BoundField
   DataField="Name"
   HeaderText="Name"  />
  <asp:BoundField
   DataField="ProductNumber"
   HeaderText="ProductNumber" />
  <asp:BoundField
   DataField="ListPrice"
   HeaderText="ListPrice"  />
  <asp:BoundField
   DataField="ProductSubcategory.Name"
   HeaderText="Subcategory" />
 </Columns>
</asp:GridView>

However, this will result in an exception stating that "A field or property with the name 'ProductSubcategory.Name' was not found on the selected data source."  So, how can we get at this data and display it on our GridView?

There are two basic approaches to this problem.  The first is to add a property to the Product class called SubcategoryName, which just returns the Name from the nested ProductSubcategory object.  We could then use this property just like the others we saw above.  This approach does indeed work just fine for our simplified example.  However, let's imagine that we have a class with many nested objects, which in turn contain other nested objects to whatever degree our particular domain requires.  It would be quite tedious to have to create properties to expose all of the necessary nested properties.

The solution to this problem is surprisingly simple.  We have seen from the sample above that BoundFields do not allow us to reference nested properties.  However, if we switch to using TemplateFields instead, then we can indeed bind to the nested properties!  So, if instead of the GridView above, we use the one below, we will get the desired effect.

<asp:GridView 
 ID="gv_allProducts"
 runat="server"
 AutoGenerateColumns="False"
 DataSourceID="ods_allProducts">
 <Columns>
  <asp:TemplateField HeaderText="Name">
   <ItemTemplate>
    <asp:Label
     ID="lbl_name"
     runat="server"
     Text='<%# Bind("Name") %>'>
    </asp:Label>
   </ItemTemplate>
  </asp:TemplateField>
  <asp:TemplateField HeaderText="Product Number">
   <ItemTemplate>
    <asp:Label
     ID="lbl_productNumber"
     runat="server"
     Text='<%# Bind("ProductNumber") %>'>
    </asp:Label>
   </ItemTemplate>
  </asp:TemplateField>
  <asp:TemplateField HeaderText="List Price">
   <ItemTemplate>
    <asp:Label
     ID="lbl_listPrice"
     runat="server"
     Text='<%# Bind("ListPrice", "{0:C}") %>'>
    </asp:Label>
   </ItemTemplate>
  </asp:TemplateField>
  <asp:TemplateField HeaderText="SubCategory">
   <ItemTemplate>
    <asp:Label
     ID="lbl_subcategoryName"
     runat="server"
     Text='<%# Bind("ProductSubcategory.Name") %>'>
    </asp:Label>
   </ItemTemplate>
  </asp:TemplateField>
 </Columns>
</asp:GridView>

So, by simply moving from BoundColumns to TemplateColumns, we can achieve the desired functionality without having to change the way we write the middle tier code.

To download a Visual Studio 2008 solution containing all of the code to demonstrate this, click here.  As mentioned above, this solution contains code that also demonstrates two other topics, which will be covered in my next two posts.