Lowry Media
ComplexBoundField - Databinding Complex Objects to a Gridview
Jul 19, 2008 11:41 AM
The number of times I have run into the following problem is astounding. I need to bind a list of complex/nested objects (those that contain other objects) to a gridview and show the values of nested objects. For example, let's say I have the following Customer class which contains a State object that holds information about in which state the customer lives. The class definitions are as follows:

public class Customer
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Title { get; set; }
    public string Company { get; set; }
    public string Address { get; set; }
    public string Address2 { get; set; }
    public string City { get; set; }
    public State State { get; set; }
    public string PostalCode { get; set; }
}
 
public class State
{
    public int StateID { get; set; }
    public string Name { get; set; }
    public string PostalCode { get; set; }
}

As you can see, the Customer object contains a State object. Here is the gridview declaration (note the DataField="State.Name"):

<asp:GridView ID="uxCustomerView" runat="server" CssClass="grid" Width="90%" GridLines="None"
                AutoGenerateColumns="false" DataKeyNames="CustomerID" RowStyle-CssClass="viewRow"
                OnRowEditing="uxCustomerView_RowEditing" OnRowDeleting="uxCustomerView_RowDeleting">
    <Columns>
        <asp:BoundField DataField="FirstName" HeaderText="First Name" />
        <asp:BoundField DataField="LastName" HeaderText="Last Name" />
        <asp:BoundField DataField="Title" HeaderText="Title" />
        <asp:BoundField DataField="Company" HeaderText="Company" />
        <asp:BoundField DataField="Address1" HeaderText="Address" />
        <asp:BoundField DataField="City" HeaderText="City" />
        <asp:BoundField DataField="State.Name" HeaderText="State"/>
        <asp:BoundField DataField="PostalCode" HeaderText="Postal Code" />
    </Columns>
</asp:GridView>

After binding a list of Customers to a gridview, the gridview will complain that the BoundField cannot decipher the DataField = "State.Name". Why this doesn't work is beyond me; I could never quite find an elegant solution on Google to assist me. Here is the solution I came up with.

First, I created a new class called ComplexBoundField that inherits from DataControlField (the base class for all gridview fields). After implementing one abstract method, I then overrode the InitializeCell method and created a new Literal control. The Literal control has a clutch DataBinding event which we can hook into in order to properly bind our complex object. Here is the ComplexBoundField class:

public class ComplexBoundField : DataControlField
{
    public string DataField { get; set; }
 
    public override void InitializeCell(DataControlFieldCell cell, DataControlCellType cellType, DataControlRowState rowState, int rowIndex)
    {
        switch (cellType)
        {
            case DataControlCellType.DataCell:
            {
                var complexDisplay = new Literal();
                complexDisplay.DataBinding += complexDisplay_DataBinding;
                cell.Controls.Add(complexDisplay);
                break;
            }
        }
        base.InitializeCell(cell, cellType, rowState, rowIndex);
    }
 
    private void complexDisplay_DataBinding(object sender, EventArgs e)
    {
        var complexDisplay = (Literal)sender;
        var container = (GridViewRow)complexDisplay.NamingContainer;
        var value = DataBinder.Eval(container.DataItem, DataField);
        if (value == null)
        {
            return;
        }
        complexDisplay.Text = value.ToString();
    }
 
    protected override DataControlField CreateField()
    {
        return new ComplexBoundField();
    }
}

As you can see, in the DataBinding method, we simply cast the sender to the Literal we defined in the InitializeCell method and find its naming container. This allows us to grab the row's DataItem which holds information specific to the Customer for each row. Finally, we set the literal's Text property to the result of calling DataBinder.Eval on the container's DataItem using the DataField we have set in the gridview declaration. Finally, we need to update the gridview declaration to show our new complex field:

<asp:GridView ID="uxCustomerView" runat="server" CssClass="grid" Width="90%" GridLines="None"
                AutoGenerateColumns="false" DataKeyNames="CustomerID" RowStyle-CssClass="viewRow"
                OnRowEditing="uxCustomerView_RowEditing" OnRowDeleting="uxCustomerView_RowDeleting">
    <Columns>
        <asp:BoundField DataField="FirstName" HeaderText="First Name" />
        <asp:BoundField DataField="LastName" HeaderText="Last Name" />
        <asp:BoundField DataField="Title" HeaderText="Title" />
        <asp:BoundField DataField="Company" HeaderText="Company" />
        <asp:BoundField DataField="Address1" HeaderText="Address" />
        <asp:BoundField DataField="City" HeaderText="City" />
        <lowrymedia:ComplexBoundField DataField="State.Name" HeaderText="State"/>
        <asp:BoundField DataField="PostalCode" HeaderText="Postal Code" />
    </Columns>
</asp:GridView>

Everything works as expected! Hopefully, this will help someone else with the problem I had.

* Disclaimer: if you need to use other functionality such as sorting, you may have to implement the code yourself when using this class. Just crack open Reflector and figure out how they handled each use case.

Return to Previous Page
Comments
Comment posted on Feb 10, 2009 5:25 AM
Peter
It's a pitty it does not work like that standard in asp.net, good work and nice simple solution!
Peter
Comment posted on May 10, 2010 3:47 PM
Steven
While this compiles and works great, I found that it breaks the designer. Everytime I add a "complexboundfield" column to a Gridview, the next time I go into design mode, I get an error on the Gridview control, like:
"Unknow server tag 'glType:complexboundfield'.
In my page I have the following directive:
<%@ Register TagPrefix="glType" Assembly="GilardiSecuritiesWorkBenchUIClientSide" Namespace="GilardiSecurities.Web.Controls.Type" %>

Any suggestions on what to change to get the designer to work?
Steven
Comment posted on May 10, 2010 4:08 PM
Brian
Sorry, I honestly never use the designer in VS. I work with the source and code modes only... I usually will map the site to an IIS website and have it open in Firefox. Hit F5 to see changes instead of flipping through the source/designer mode.
Brian
Add Comment