So recently, I was given the challenge of binding a complex business object to a GridView... I didn't think it would be tricky, but I did find a few niggles along the way. I thought I would share them so anybody else attempting to do this can pick up some lessons learned. The following is my implementation of this task...
This example displays a list of courses. Each course has an ID, a name and a production status. The production status' will be rendered as images within the gridview. Below is the designer structure of the grid..
<asp:GridView ID="grdBantyGridView"
CssClass="gridView"
rowstyle-borderstyle="none"
GridLines="None" BorderStyle="None"
RowStyle-CssClass="row"
runat="server"
ShowFooter="True"
AutoGenerateColumns="False"
ShowHeader="True"
AlternatingRowStyle-CssClass="alternate"
AllowSorting="True"
UseAccessibleHeader="True"
onrowdatabound="grdBantyGridView_RowDataBound"
onsorting="grdBantyGridView_Sorting"
OnPageIndexChanging="grdBantyGridView_PageIndexChanging"
AllowPaging="True"
PageSize="10"
PagerSettings-Mode="NumericFirstLast"
PagerSettings-PageButtonCount="10"
DataKeyNames="ID" EnableViewState="False"
>
<HeaderStyle CssClass="gridViewHeader" />
<RowStyle BorderStyle="None" CssClass="row" />
<Columns>
<asp:BoundField DataField="ID" Visible="False" />
<asp:TemplateField AccessibleHeaderText="Name" HeaderText="Name"
SortExpression="Name">
<ItemTemplate>
<asp:Label ID="lblName" runat="server"
Text='<%# Eval("Name") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField AccessibleHeaderText="Description" HeaderText="Description"
SortExpression="Description">
<ItemTemplate>
<asp:Label ID="lblDescription" runat="server"
Text='<%# Eval("Description") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField AccessibleHeaderText="Status" HeaderText="Status"
SortExpression="Status">
<ItemTemplate>
<asp:Image ID="imgStatus" runat="server"
ImageUrl='<%# GetCourseStatusUrl((Banty.Library.DTO.CourseStatus)Eval("Status")) %>' />
</ItemTemplate>
</asp:TemplateField>
</Columns>
<FooterStyle CssClass="gridViewFooter" />
<EmptyDataTemplate>
No data available.
</EmptyDataTemplate>
<PagerStyle height="5px" verticalalign="Bottom" horizontalalign="Center" />
<AlternatingRowStyle CssClass="alternate" />
</asp:GridView>
Things to note:
AutoGenerateColumns - This gives us more flexibility over what columns appear when we bind out business objects.
AllowSorting - Allows the sorting, but because we have disabled AutoGenerateColumns, we need to override the 'onsorting' event.
AllowPaging - Allow the paging. We must override the 'OnPageIndexChanging' event to make this work.
DataKeyNames - Set this so that we can retrieve each row during the 'onrowdatabound' event. This will store a datakey against each row. I am using this to enable javascript highlights of rows in this example.
For each column in the Gridview definition, be sure to add a
SortExpression value. This helps us in the code-behind to identify while column is requesting the sort.
The above points are key to creating this pagable and sortable grid, and must be taken into consideration... Lets now have a look at the code behind and how we fit this all together...
private List<Banty.Library.DTO.Course> courses = null;
#region >>> Properties
public List<Banty.Library.DTO.Course> Courses { get { return this.courses; } set { this.courses = value; } }
#endregion
protected void Page_Load(object sender, EventArgs e)
{
}
/// <summary>
/// Initialises the user control.
/// </summary>
public void InitControl()
{
if (this.Courses != null)
{
this.SetDataSource(true);
pnlNoData.Visible = false;
pnlGridView.Visible = true;
}
else
{
pnlNoData.Visible = true;
pnlGridView.Visible = false;
}
}
/// <summary>
/// Sets the datasource to the gridview.
/// </summary>
private List<Banty.Library.DTO.Course> SetDataSource(bool dataBind)
{
// Bind apps to the grid view.
this.grdBantyGridView.DataSource = this.Courses;
// Bind if requested
if (dataBind)
this.grdBantyGridView.DataBind();
return this.Courses;
}
/// <summary>
/// Performs actions as the row data is bound.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected void grdBantyGridView_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
int uniqueID = 0;
if (Int32.TryParse(this.grdBantyGridView.DataKeys[e.Row.RowIndex].Value.ToString(), out uniqueID))
{
// Add applicationID to row
e.Row.Attributes["uniqueid"] = uniqueID.ToString();
}
// So that we can distinguish rows
e.Row.Attributes.Add("OnMouseUp", "grdBantyGridView_OnMouseUp(" + e.Row.ClientID + ");");
}
}
/// <summary>
/// Provides an image URL for value.
/// </summary>
/// <param name="input">data value</param>
/// <returns>A URL to the value icon</returns>
protected string GetCourseStatusUrl(CourseStatus input)
{
string result = string.Empty;
switch (input.ID)
{
case 1:
result = "../images/icon-no.gif";
break;
case 2:
result = "../images/icon-yes.gif";
break;
case 3:
result = "../images/icon-verified.gif";
break;
}
return result;
}
/// <summary>
/// Handles the sorting of the gridview columns
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected void grdBantyGridView_Sorting(object sender, GridViewSortEventArgs e)
{
// Determine weather column is valid
string[] validFields = new string[] { "Name", "Description", "Status"};
// if we have a valid field to sort....
if (validFields.Contains(e.SortExpression))
{
// Get Sort direction
string sortDir = this.GetSortDirection(e.SortExpression);
List<Banty.Library.DTO.Course> courses = this.SetDataSource(false);
// Filter on sort expression...
switch (e.SortExpression)
{
case "Name":
if (sortDir.Equals("ASC"))
courses.Sort(delegate(Banty.Library.DTO.Course c1, Banty.Library.DTO.Course c2) { return c1.Name.CompareTo(c2.Name); });
else
courses.Sort(delegate(Banty.Library.DTO.Course c1, Banty.Library.DTO.Course c2) { return -c1.Name.CompareTo(c2.Name); });
break;
case "Description":
if (sortDir.Equals("ASC"))
courses.Sort(delegate(Banty.Library.DTO.Course c1, Banty.Library.DTO.Course c2) { return c1.Description.CompareTo(c2.Description); });
else
courses.Sort(delegate(Banty.Library.DTO.Course c1, Banty.Library.DTO.Course c2) { return -c1.Description.CompareTo(c2.Description); });
break;
case "Status":
if (sortDir.Equals("ASC"))
courses.Sort(delegate(Banty.Library.DTO.Course c1, Banty.Library.DTO.Course c2) { return c1.Status.Status.CompareTo(c2.Status.Status); });
else
courses.Sort(delegate(Banty.Library.DTO.Course c1, Banty.Library.DTO.Course c2) { return -c1.Status.Status.CompareTo(c2.Status.Status); });
break;
}
// Rebind the gridview
this.grdBantyGridView.DataBind();
}
}
/// <summary>
/// Handle page changing on the grid.
/// </summary>
/// <param name="sender">This parameter is not used.</param>
/// <param name="e">This parameter is not used.</param>
protected void grdBantyGridView_PageIndexChanging(object sender, GridViewPageEventArgs e)
{
// Update data source
this.SetDataSource(false);
this.grdBantyGridView.PageIndex = e.NewPageIndex;
this.grdBantyGridView.DataBind();
}
// This is sort the column, by default, asc.
// Unless, the column has been sorted already...
// then it will sort the column in the opposite direction.
private string GetSortDirection(string column)
{
// By default, set the sort direction to ascending.
string sortDirection = "ASC";
// Retrieve the last column that was sorted.
string sortExpression = ViewState["SortExpression"] as string;
if (sortExpression != null)
{
// Check if the same column is being sorted.
// Otherwise, the default value can be returned.
if (sortExpression == column)
{
string lastDirection = ViewState["SortDirection"] as string;
if ((lastDirection != null) && (lastDirection == "ASC"))
{
sortDirection = "DESC";
}
}
}
// Save new values in ViewState.
ViewState["SortDirection"] = sortDirection;
ViewState["SortExpression"] = column;
return sortDirection;
}
}
We can also add javascript to apply styles to the selected row
//
// Handles highlighting table rows on mouseup event.
//
function grdBantyGridView_OnMouseUp(row) {
// Reset grid styles
ResetGrid();
// Add selected class
row.setAttribute('class', 'selected');
}
function ResetGrid()
{
// Remove selected from all rows that have a unique attribute...
$("table.gridView tr[uniqueid]").removeClass("selected");
// Now add the alternate style to every 2nd row...
for (var i = 1; i < $("table.gridView tr[uniqueid]").length + 1; i++)
{
if (i % 2 == 0)
$("table.gridView tr[uniqueid]")[i-1].setAttribute('class', 'alternate');
}
}