ASP.NET: GridView and ObjectDataSource with custom objects

The GridView control is one of the great time savers of ASP.NET.  Sorting, paging, editing, deleting, and even AJAX (set EnableSortingAndPagingCallbacks to True) is included out of the box.  The ObjectDataSource is the other half of the magic, connecting the GridView to the data and managing any filter controls.  When using DataSets and TableAdapters, everything is a dream, but when using your own custom objects the wizards and property pages act like you don’t exist.  The good news is with a little extra code, we can have our custom objects act just like fancy DataSets.

First thing you’ll need is a class to represent a row in the GridView.  For the GridView to show the fields in the “Edit Fields…” design time dialog, you’ll need to expose the fields as Properties. A very simple class for a Person could look like this:

using System;
using System.ComponentModel;

public class Person: IEquatable<Person> {
    
private String _name; [DataObjectField(true)] public String Name { get { return _name; } set { _name = value; } } private String _title; [DataObjectField(false)] public String Title { get { return _title; } set { _title = value; } } public Person() { }
public Person(String Name, String Title) { this.Name = Name; this.Title = Title; } #region IEquatable<Person> Members public bool Equals(Person other) { return this.Name.Equals(other.Name); } #endregion }

If you’ve not seen the [ ] syntax before, these are Attributes.  This simple markup tells the compiler additional information about the entity, which is then used to generate additional code.  A very common Attribute is Serializable, which means the object can be saved.  A class marked [Serializable] would have code added to save and load a saved object automatically.  In our case we are using System.ComponentModel.DataObjectField to markup the field for the GridView.  The true parameter marks the field as a primary key, which will automatically add the field name to the “DataKeyNames” list of the GridView.  Other options you can add are isIdentity, isNullable, and length.  Note, this just describes the field, it’s up to you to enforce these constraints.

The IEquatable<> part isn’t something for the GridView, but to help us manage our custom object.  This Interface has one method, Equals, which is used when comparing one object to another to determine if they are the same.  In our case, we are going to assume they are equal if the have the same Name (our Primary Key).

Now let us look at our adapter class:

using System;
using System.Web;
using System.Collections.Generic;
using System.ComponentModel;

public class PersonList : List<Person> { };

[DataObject(true)]
public class PersonAdapter {

    private PersonList _data {
        get {
            if (HttpContext.Current.Session["_data"] == null) return new PersonList();
            else return (PersonList)HttpContext.Current.Session["_data"];
        }
        set { HttpContext.Current.Session["_data"] = value; }
    }

    [DataObjectMethod(DataObjectMethodType.Select, true)]
    public PersonList GetAll() {
        return _data;
    }

    [DataObjectMethod(DataObjectMethodType.Delete, true)]
    public void DeletePerson(Person person) {
        PersonList people = _data;
        people.Remove(person);
        _data = people;
    }

    [DataObjectMethod(DataObjectMethodType.Update, true)]
    public void UpdatePerson(Person person) {
        PersonList people = _data;
        people.Remove(person);
        people.Add(person);
        _data = people;
    }

    [DataObjectMethod(DataObjectMethodType.Insert, true)]
    public void InsertPerson(Person person) {
        PersonList people = _data;
        people.Add(person);
        _data = people;
    }
}

First thing to notice is the use of Generics – we are using a List<> to represent a set of Person objects.  Inheriting from List<> in this manner buys us some ease in data management, but the List<> class already supports paging by controls such as the GridView.  Since it’s subclassed, our users won’t need to know anything about Generics to use the adapter.  The reason we implemented IEquatable above is to support the List.Remove method, used in updates and deletes.

Like before, we use some Attributes to let the Visual Studio designer know about our class.  First, we mark the class with System.ComponentModel.DataObject(true) – this tells Visual Studio to list the class in the Choose a Business Object screen of the configure datasource wizard.  System.ComponentModel.DataObjectMethod above our methods tell the wizard what type of method (Select, Delete, Edit, or Insert) and if it’s the default method for that type.

If you want to see all this in action, create a website with the two classes above, and then a form with the following controls:

<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" 
    DataObjectTypeName="Person" TypeName="PersonAdapter" 
    DeleteMethod="DeletePerson" InsertMethod="InsertPerson"
    SelectMethod="GetAll" UpdateMethod="UpdatePerson" />
    
<asp:GridView ID="GridView1" runat="server" AllowPaging="True" 
    DataKeyNames="Name" DataSourceID="ObjectDataSource1" 
    AutoGenerateDeleteButton="True" AutoGenerateEditButton="True"
    AutoGenerateColumns="True" />
    
<asp:DetailsView ID="DetailsView1" runat="server" AutoGenerateRows="False" 
    DataKeyNames="Name" DataSourceID="ObjectDataSource1"
     DefaultMode="Insert" AutoGenerateInsertButton="True" >
    <Fields>
        <asp:BoundField DataField="Title" HeaderText="Title"/>
        <asp:BoundField DataField="Name" HeaderText="Name" />
    </Fields>
</asp:DetailsView>

As you can see, using the methods above we need very little on our form.  In a future article I’ll take things further, including enabling sorting on our custom objects.

  • http://www.dylanwolf.com/ Dylan Wolf

    Pretty interesting. It seems a lot cleaner creating a set of classes and using ObjectDataSource instead of directly hooking up GridViews, etc. to TableAdapters, even though it removes a lot of the automatic functionality of GridViews.

    I’m really interested in the sorting, because my solution to the problem was to extend the GridView to use reflection to sort the list returned by ObjectDataSource based on the property name listed as the sort expression.

  • http://www.vinull.com Mike

    Each way has it’s place. If my data is small and 100% in a database, then "nekkid" tableAdapters it is!

    The follow up with sorting is posted.

  • http://www.dylanwolf.com/ Dylan

    So… completely unrelated, how are you doing the syntax highlighting in your posts?

  • http://www.vinull.com Mike

    I post with Windows Live Writer (I implemented MovableType, Blogger, and MetaWebLog APIs to support blog tools) and have the "Insert from Visual Studio" plug in. There is also a generic "Insert Code" plugin I use as a fall back.

  • CodeDemon

    Thanks a lot!
    Finally a small sample with custom objects that is in a way I can adopt in my application.
    Got very frustrated the last 3 hours of try and error on my own

  • TaLz

    hi,
    i tried implementing your example however i put the PersonAdapter and Person classes in a seperate project and not in the app_code folder.
    in the constructor function i get the list from the database and assign it to a class variable. in the "GetAll" function i return it. the grid displays the values from the database correctly.
    however when i call the delete method i get an emtpy object of type person.
    do i need to create a session variable to hold the list as well?
    i was under the impression that the objectdatasource takes care of the binding between the form and the server side

  • http://www.vinull.com Mike

    If you use another project you’ll need to make sure the website project has a reference to the other project, and rebuild the site when you make changes to insure the DLL in the /bin folder is updated.

    If you want to email me (address under "contact" below) your code I can take a look at it.

  • http://www.Spartansoft.net Chris Poind

    So is it safe to assume that we need to create a seperate mechanism for inserting when the GridView is used? I can’t seem to find an Insert template off that thing. Another question, if I figure out how to populate the InsertParams off the ObjectDataSource from from controls outside the Gridview, will it automatically update considering I am going through ObjectDataSource? Struggling a bit. Thanks for your time!

  • http://www.Spartansoft.net Chris Poind

    Nice article btw.

  • http://www.vinull.com Mike

    Thanks Chris!

    Yes, normally you wouldn’t be inserting into a GridView – Inserts are supported in the DetailsView and FormView. These controls will take care of the databinding, and you can set the DefaultMode property to Insert to have the insert form always visible.

  • Thomas

    Hi Chris;

    Is there a way to use this method with a data adapter that takes arguments? I need to populate a gridview based on a foreign key.

    Thanks!

  • Thomas

    Hi Chris;

    Well, I got the answer to my first question. Include an argument in the Select function, then in the ObjectDataSource _Selecting event, set the select parameter.

    Here’s my code

    In the custom object:
    public PersonList Get_By_JobID(int iJobID)
    {
    mdsPersonsTableAdapters.Persons taPersons = new mdsPersonsTableAdapters.Persons();
    mdsPersons.PersonsDataTable dtPersons = new mdsPersons.PersonsDataTable();

    taPersons.Fill_By_JobID(dtPersons, iJobID);

    PersonList oslPersons = new PersonList();

    //doesnt appear to be any easy way to cast from datatable to list,
    //so it’s brute force
    foreach (mdsPersons.PersonsRow drPerson in dtPersons.Rows)
    {
    Person osPerson = new Person(
    drPerson.PersonsID,
    drPerson.JobID,
    drPerson.Person);

    oslPersons.Add(osPerson);
    }

    return oslPersons;
    }

    where the mds objects are datasets.

    In the code-behind of the page:

    protected void odsPersons_Selecting(object sender, ObjectDataSourceSelectingEventArgs e)
    {
    mdsJobsTableAdapters.Jobs taJobs = new mdsJobsTableAdapters.Jobs();
    mdsJobs.JobsDataTable dtJobs = new mdsJobs.JobsDataTable();
    taJobs.Fill_Jobs_By_AnotherID(dtJobs, Convert.ToInt32(Session["AnotherID"].ToString()));

    e.InputParameters.Add("iJobID", dtJobs[0].JobID);
    }

    But now my update doesn’t work. Stepping through, I find that the "person" object passed to the UpdatePerson function has all empty values (in my case, all zero’s since my code has three integer fields).

    Any ideas?

    I forgot to say in my previous, this article has been very useful for me, as previously I was trying to do all the handling manually in the various events, which in theory sounds not too bad, but it wasn’t working out well. So thanks!

    Thomas

  • http://www.vinull.com Mike

    Hi Thomas,

    First observation is why are you using the custom object – you can map to the table adapter directly? This will cut down on the need for much of the code.

    Another thing to remember, the ObjectDataSource Selecting event is not always fired – if the datasource is loaded from cache (i.e. viewstate) it won’t fire Selecting because it didn’t actually do a select.

    If that doesn’t help track down the problem, email me the code and I’ll take a look – my address is in the footer.

    I’m Mike BTW =p

  • Thomas

    Hi Mike;

    Thanks for the reply!

    I’m using a custom object because I don’t want the updates to occur when I add/edit/delete items in the gridview. The gridview contains optional data stored in a different table from the primary data (for one record in the parent table) collected on the page. So I don’t want to add the records to the datatable attached to the gridview until the user clicks the Save button to save all data on the page, including the zero or more records for the optional data.

    I’m accomplishing this by storing the datatable in a session variable. The session variable is an XML dataset (I’ve created xml datasets to work with all the tables in the db). I use the custom object to insert/edit/delete rows in the session variable without invoking .Update(). Update will be invoked when the user saves all the data at once.

    Hope that makes sense. Maybe the code I posted will help too.

    Thanks!
    -T

  • Thomas

    "I’m Mike BTW =p "

    Oops.. sorry LOL. I think I was talking to my boss at the time, and his name is Chris.

  • Waldo

    I am curious…. Your Person class assumes the name is the PK. How would one handle the case where the name is changed in Editing mode? I am asking since the .Equals() method will fail to remove the correct person record as the name changed.

  • Jose

    Complex binding is not supported by ObjectDataSource. It seems a good a idea with a simple custom class

  • Mukesh

    Hi,
    Thanks .nice codind. I have another question form this code.if i need to remove multiple rows that are checked in check box,how we do dat. please rplr

  • http://dotnetarchitect.wordpress.com Binu Thayamkery

    You can get the automatic sort features, if the datasource (objectdatasource) is bound to a DataTable/DataView. So if you are dealing with a custom object or a generic list of custom object, you make a datatable from it and use it to bind to gridview. Its not a dirty trick, with help of reflection you can get some generic code written, see my post here -> http://dotnetarchitect.wordpress.com/2009/04/07/gridview-sorting-trick-when-using-object-datasource-with-custom-objects/

  • Dave

    Great article ;o)

    I’m stuck on a slightly more complex issue and can’t seem to find a good answer.

    I have an object (Person) that has a child collection (List<TelephoneNumber>). I have a FormView with a parent ODS bound to the Person. From within the FormView I have a GridView that is bound to a child ODS for the telephone numbers.

    When adding/removing telephone numbers I want it to update the Person’s child collection in memory, but only when the FormView Save button is pressed do I want it to save.

    Any ideas?

  • http://www.vinull.com Mike

    Hi Dave,

    You’ll need to get rid of the ODS attached to the GridView and deal with the update events by hand. It will be messy, but can be done.

  • Dave

    Thanks fella.

    I’m working on a solution that caches the main object within the OnObjectCreating method, but unfortunately it’s a bit messy as you say.

  • vijay

    is it possible to store the users information instead of using session variable?

  • Rob

    This is brilliant and has saved me days of work, thankyou.