Click here to Skip to main content
15,867,453 members
Articles / Web Development / ASP.NET

Some Tips and Tricks for Using an ObjectDataSource With a GridView

Rate me:
Please Sign up or sign in to vote.
4.85/5 (43 votes)
4 Jul 2006CPOL6 min read 485.6K   1.1K   103   135
This article uses a generic collection as the data source for a GridView. Some code is requried to get the sorting to work. There is an example of using the pager template along with a simple GridView printer.

Sample Image - GridViewObjectDataSource.gif

Introduction

This article attempts to show a few tricks with the new GridView that we have in ASP.NET 2.0. This article addresses using the ObjectDataSource to populate the GridView. In this example, the ObjectDataSource returns a generic collection of an object. There is some special coding that needs to happen to get the sorting of columns to work. I will also show how to use the pager template to do custom navigation. I have also included a simple GridView printing example.

Background

I have been meaning to write an article on the GridView for sometime. I really like the improvements .NET 2.0 has over the DataGrid. Using a GridView in combination with a SqlDataSource or ObjectDataSource will give you a lot of functionality without having to write a lot of code.

The Code

First, let's take a look at the People class I put together. Notice that I just have a few properties on it. I just needed something simple for this example, so this is what I came up with.

Class diagram of People class

Next, let's talk about ObjectDataSource. Both SqlDataSource and ObjectDataSource give you a lot of functionality when you point your GridView to one of these objects for data. I have only implemented the Select method for this example, but these objects also allow you to implement Insert, Update, and Delete. With SqlDataSource, you point the method to a database Stored Procedure, table, or query. With an ObjectDataSource, which is what is used in this example, you point the Select method to a type and a method name. Notice that these objects can implement caching. Here's the HTML:

ASP.NET
<asp:ObjectDataSource id=ObjectDataSource1 CacheExpirationPolicy="Sliding" 
    CacheDuration="300" TypeName="DataAccess" 
    SelectMethod="GetData" runat="server">
 <SELECTPARAMETERS>
  <asp:Parameter Direction="input" Type="string" Name="p_sortExpression">
  </asp:Parameter>
  <asp:Parameter Direction="input" Type="string" Name="p_sortDirection">
  </asp:Parameter>
 </SELECTPARAMETERS>
</asp:ObjectDataSource>

Once the ObjectDataSource is set up, you just need to point the GridView DataSourceID equal to the ObjectDataSource name. I am going to have my ObjectDataSource method (GetData) return a List<PEOPLE>. This generic collection has one drawback. The sorting doesn't work automatically, like it does if you return a DataSet, DataTable, or DataView from an ObjectDataSource. If you try sorting, you will actually get this exception:

The data source 'ObjectDataSource1' does not support sorting with 
  IEnumerable data. Automatic sorting is only supported with 
  DataView, DataTable, and DataSet.

So the way I got around this exception is I created an event handler for the GridView.Sorting event. In this event, I set the ObjectDataSource SortExpression and SortDirection parameters to the values I get from the GridView from the sorting event. Finally, I cancel the sorting event so I don't get the exception. Here is the event code:

C#
protected void GridView1_Sorting(object sender, GridViewSortEventArgs e)
{
    //Since we are changing the parameters we will not use the cached
    //object datasource we will call the selecte method again.
    if (ObjectDataSource1.SelectParameters[0].DefaultValue == null ||
        ObjectDataSource1.SelectParameters[1].DefaultValue == null ||
        ObjectDataSource1.SelectParameters[0].DefaultValue != 
        e.SortExpression ||
        ObjectDataSource1.SelectParameters[1].DefaultValue == "Desc")
    { //sort direction
        ObjectDataSource1.SelectParameters[1].DefaultValue = "Asce";
    }
    else if (ObjectDataSource1.SelectParameters[1].DefaultValue == "Asce")
    {
        ObjectDataSource1.SelectParameters[1].DefaultValue = "Desc";

    }

    //Which column to sort on.
    ObjectDataSource1.SelectParameters[0].DefaultValue = e.SortExpression;

    //We have to do this or we will get an exception
    e.Cancel = true;
}

The next thing I need is to implement the ObjectDataSource Select method. It looks like this:

C#
public static List<PEOPLE> GetData(string p_sortExpression, string p_sortDirection)
{
    List<PEOPLE> peoples = new List<PEOPLE>();
    //Normally here you would access your database to populate this object.
    //Since I want this example not to have any need to connect to a database
    //I am just creating the data.
    //Note when caching the objectdatasource a chance in parameters being
    //passed in will case the select method to be called again.
    #region Creating data
    peoples.Add(new People(1, "Bart", "Long", "Mower", 10.00M, 18));
    ...
    peoples.Add(new People(21, "Gary", "Black", "Doctor", 46.50M, 29)); 
    #endregion

    //We sort the generic list if requested too
    if (p_sortExpression != null && p_sortExpression != string.Empty)
    {
        peoples.Sort(new PeopleComparer(p_sortExpression));
    }

    //Now that we have sorted check to see if the sort direction is desc
    if (p_sortDirection != null && p_sortDirection == "Desc")
    {
        peoples.Reverse();
    }

    return peoples;
}

Next, we need to look at the PeopleComparer that is used to sort the generic collection. I use Reflection so that I can just find the property that is passed into the comparer. Here is the code:

C#
public int Compare(People x, People y)
{
    Type t = x.GetType();
    PropertyInfo val = t.GetProperty(this.PropertyName);
    if (val != null)
    {
        return Comparer.DefaultInvariant.Compare(val.GetValue(x,null), 
                        val.GetValue(y,null));
    }
    else
    {
        throw new Exception(this.PropertyName + 
              " is not a valid property to sort on.  " + 
              "It doesn't exist in the Class.");
    }
}

Let's move on to navigation. I really like the pager functionality they have added to the GridView. There are still a few things I would like my navigation to do. First is to have a label that tells me which page I am on out of how many pages. The second is to have the first and last buttons disappear when you are on the first and last pages, respectively. Finally, it is also nice to have a dropdown that lets the user choose how many items per page are shown. The thing to note about doing your own navigation in a pager template is that you just need to mark the controls with the correct CommandName, which should be "Page", and the correct CommandArgument, which will be "First", "Prev", "Next", or "Last". If you implement these two things, paging will work as long as you have a SqlDataSource or a ObjectDataSource behind the scenes. This is where it is nice to implement some sort of caching on these objects so you don't hit your Select method every time you navigate your GridView. Here's the HTML for the pager template:

HTML
<PagerTemplate>
<table id="tbPager" width="100%">
<tr>
<td>Page <asp:Label ID="lbCurrentPage" runat="server"></asp:Label> of 
<asp:Label ID="lbTotalPages" runat="server"></asp:Label>
</td>
<td>
<asp:DropDownList ID="ddlPageItems" runat="server" AutoPostBack="True" 
    OnSelectedIndexChanged="ddlPageItems_SelectedIndexChanged">
    <asp:ListItem>5</asp:ListItem>
    <asp:ListItem Selected="True">10</asp:ListItem>
    <asp:ListItem>20</asp:ListItem>
    </asp:DropDownList></td>
<td align="right">
 <asp:LinkButton ID="lbtnFirst" runat="server" CommandName="Page" 
  CommandArgument="First" Text="<<"></asp:LinkButton> 
 <asp:LinkButton ID="lbtnPrev" runat="server" CommandName="Page" 
  CommandArgument="Prev" Text="Prev"></asp:LinkButton> 
 <asp:LinkButton ID="lbtnNext" runat="server" CommandName="Page" 
  CommandArgument="Next" Text="Next"></asp:LinkButton> 
 <asp:LinkButton ID="lbtnLast" runat="server" CommandName="Page" 
  CommandArgument ="Last" Text=">>"></asp:LinkButton>
</td>
</tr>
</table>
</PagerTemplate>

Next, I write a handler for the GridView.PreRender event. This is where I show or hide the first and last navigation buttons and where I set the current page and total pages. There is one thing to note about the pager template. Since the pager template can be both a top and bottom pager control, you have to check both if you are using them. You will find the control resolves to TopPagerRow and BottomPagerRow off of the GridView control. In this example, I only use the TopPagerRow. I have found that when using both top and bottom, some of the control events don't work properly. Like in the case of the dropdownlist event for the number of items on a page. It seems to work the first time on the BottomPagerRow, but doesn't fire after that. Anyway, here is the pre-render event for the GridView:

C#
protected void GridView1_PreRender(object sender, EventArgs e)
{
    //This logic displays the Page # of # in the pager template
    if (GridView1.TopPagerRow != null)
    {
      ((Label)GridView1.TopPagerRow.FindControl("lbCurrentPage")).Text = 
        (GridView1.PageIndex + 1).ToString();
      ((Label)GridView1.TopPagerRow.FindControl("lbTotalPages")).Text = 
        GridView1.PageCount.ToString();

       //This makes the first and last button disappear when on the 
       //first and last pages.
      ((LinkButton)GridView1.TopPagerRow.FindControl("lbtnFirst")).Visible = 
        GridView1.PageIndex != 0;
      ((LinkButton)GridView1.TopPagerRow.FindControl("lbtnLast")).Visible = 
        GridView1.PageCount != (GridView1.PageIndex +1);

      DropDownList ddlist = 
         (DropDownList)GridView1.TopPagerRow.FindControl("ddlPageItems");
      ddlist.SelectedIndex = 
         ddlist.Items.IndexOf(ddlist.Items.FindByValue(
         ViewState["DropDownPageItems"].ToString()));
    }
}

I also have an example of simple GridView printing. The idea is to put the columns of the GridView you want to print into a session variable. Then, in another session variable, you put the data source. So in the Print GridView page, you just have to add the columns and set the data source. Here is the Print button method:

C#
protected void btnPrint_Click(object sender, EventArgs e)
{
    Session["PrintGridviewColumns"] = this.GridView1.Columns;
    Session["PrintGridViewDataSource"] = this.ObjectDataSource1.Select();
    //Do some java script to bring up the Printing form in a different 
    //browser window
    StringBuilder sb = new StringBuilder();
    sb.Append("<script>");
    sb.Append(Environment.NewLine);
    sb.Append("window.open(\"PrintGridview.aspx\",\"Print\",\"top=5,left=5\");");
    sb.Append(Environment.NewLine);
    sb.Append("</script>");

    ClientScript.RegisterStartupScript(this.GetType(), "print", sb.ToString());
}

Here is the page load for PrintGridView.aspx:

C#
protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack && Session["PrintGridviewColumns"] != null &&
        Session["PrintGridViewDataSource"] != null)
    {
        //Add the columns to the print gridview
        foreach (DataControlField dcf in (DataControlFieldCollection)
                        Session["PrintGridviewColumns"])
        {
            this.GridView1.Columns.Add(dcf);
        }

        //Set the data source and databind
        this.GridView1.DataSource = Session["PrintGridViewDataSource"];
        this.GridView1.DataBind();

        //Do some java script to bring up the printer dialog
        StringBuilder sb = new StringBuilder();
        sb.Append("<script language="javascript">");
        sb.Append("window.print();");
        sb.Append("</script>");

        ClientScript.RegisterStartupScript(this.GetType(),"print", sb.ToString());
        Session["PrintGridviewColumns"] = null;
        Session["PrintGridViewDataSource"] = null;
    }
}

Another tip has to do with the DataFormatString in a bound column. In my example, the DollarsPerHour column is formatted to have a dollar sign and a decimal point. The tip here is that for that column, you must set HTMLEncode = false. Otherwise, the formatting doesn't work.

Another tip is for storing the row key in the GridView, but not displaying it. I create a template column and set its Visible property to false. Then in the item and edit template, I have a label that is bound to the key name. This way, I can access the key later on.

C#
foreach (GridViewRow tmpGVR in p_gridview.Rows)
{
    tmpLBKey = (Label)tmpGVR.FindControl("lbKey");
} //foreach

One last thing on the GridView printing example. If you have template columns, you must use DataBinder.Eval(Container.DataItem, "Key") verses the newer Bind("Key"). For some reason, when you pass the columns from your GridView to the printing page, it won't bind right to the Bind("Key") syntax.

Finally, I just added this in since I thought it was a good thing to know. If you want to search a generic collection and find a certain object in the collection based off a property in the object, here is one way of doing it. In this example, you pass in the key value and get back a People object if it can be found in the generic collection.

C#
private People FindByID(int p_key)
{
    List<PEOPLE> peoples = (List<PEOPLE>)this.ObjectDataSource1.Select();
    return peoples.Find(delegate(People p) {return p.Key == p_key;});
}

In this code, you see an example of an anonymous method delegate.

Conclusion

Well, I hope you have learned some new things about the GridView and some tips to help you make your implementation of the GridView work in whatever way your situation calls for.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
I started my programmer career over 26 years ago doing COBOL and SAS on a MVS mainframe. It didn't take long for me to move into windows programming. I started my windows programming in Delphi (Pascal) with a Microsoft SQL server back end. I started working with vb.net when the beta 2 came out in 2001. After spending most of my programming life as a windows programmer I started to check out asp.net in 2004. I achieved my MCSD.net in April 2005. I have done a lot of MS SQL database stuff. I have a lot of experience with Window Service and Web services as well. I spent three years as a consultant programing in C#. I really enjoyed it and found the switch between vb.net and C# to be mostly syntax. In my current position I am programming in C# working on WPF and MSSql database stuff. Lately I have been using VS2019.

On a personal note I am a born again Christian, if anyone has any questions about what it means to have a right relationship with God or if you have questions about who Jesus Christ is, send me an e-mail. ben.kubicek[at]netzero[dot]com You need to replace the [at] with @ and [dot] with . for the email to work. My relationship with God gives purpose and meaning to my life.

Comments and Discussions

 
GeneralExcellent article - what about sqldatasource Pin
sscalish9-Nov-09 6:02
sscalish9-Nov-09 6:02 
GeneralRe: Excellent article - what about sqldatasource Pin
kubben9-Nov-09 6:16
kubben9-Nov-09 6:16 
General[Message Deleted] Pin
sscalish9-Nov-09 8:07
sscalish9-Nov-09 8:07 
GeneralRe: Excellent article - what about sqldatasource Pin
kubben9-Nov-09 8:17
kubben9-Nov-09 8:17 
GeneralRe: Excellent article - what about sqldatasource Pin
sscalish9-Nov-09 9:00
sscalish9-Nov-09 9:00 
GeneralRe: Excellent article - what about sqldatasource Pin
kubben9-Nov-09 9:02
kubben9-Nov-09 9:02 
I am glad you got it working.

Ben
GeneralRe: Excellent article - what about sqldatasource Pin
sscalish9-Nov-09 9:43
sscalish9-Nov-09 9:43 
GeneralRe: Excellent article - what about sqldatasource Pin
kubben9-Nov-09 12:56
kubben9-Nov-09 12:56 
GeneralPaging and Sorting Events Pin
fellippe31-Mar-09 23:42
fellippe31-Mar-09 23:42 
GeneralRe: Paging and Sorting Events Pin
kubben1-Apr-09 0:21
kubben1-Apr-09 0:21 
GeneralBi-directional sorting doesn't work Pin
count_rugen25-Feb-08 9:30
count_rugen25-Feb-08 9:30 
GeneralRe: Bi-directional sorting doesn't work Pin
kubben25-Feb-08 9:51
kubben25-Feb-08 9:51 
GeneralRe: Bi-directional sorting doesn't work Pin
count_rugen25-Feb-08 10:11
count_rugen25-Feb-08 10:11 
GeneralRe: Bi-directional sorting doesn't work Pin
kubben25-Feb-08 10:21
kubben25-Feb-08 10:21 
GeneralRe: Bi-directional sorting doesn't work Pin
count_rugen25-Feb-08 10:34
count_rugen25-Feb-08 10:34 
GeneralRe: Bi-directional sorting doesn't work Pin
kubben25-Feb-08 15:59
kubben25-Feb-08 15:59 
GeneralRe: Bi-directional sorting doesn't work Pin
mr_gold10-May-08 6:50
mr_gold10-May-08 6:50 
GeneralRe: Bi-directional sorting doesn't work Pin
kubben10-May-08 8:17
kubben10-May-08 8:17 
GeneralRe: Bi-directional sorting doesn't work Pin
Member 382185921-Aug-08 10:22
Member 382185921-Aug-08 10:22 
QuestionData source is an invalid type. It must be either an IListSource, IEnumerable, or IDataSource. Pin
dsmportal12-Jan-08 3:07
dsmportal12-Jan-08 3:07 
GeneralRe: Data source is an invalid type. It must be either an IListSource, IEnumerable, or IDataSource. Pin
kubben12-Jan-08 4:18
kubben12-Jan-08 4:18 
GeneralRe: Data source is an invalid type. It must be either an IListSource, IEnumerable, or IDataSource. Pin
dsmportal12-Jan-08 5:01
dsmportal12-Jan-08 5:01 
GeneralRe: Data source is an invalid type. It must be either an IListSource, IEnumerable, or IDataSource. Pin
kubben12-Jan-08 5:35
kubben12-Jan-08 5:35 
GeneralRe: Data source is an invalid type. It must be either an IListSource, IEnumerable, or IDataSource. Pin
dsmportal12-Jan-08 6:00
dsmportal12-Jan-08 6:00 
GeneralRe: Data source is an invalid type. It must be either an IListSource, IEnumerable, or IDataSource. Pin
kubben12-Jan-08 9:28
kubben12-Jan-08 9:28 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.