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.

Posted By Mike On Monday, April 16, 2007
Filed under asp.net gridview objectdatasource | Comments (24)

Submit this story to DotNetKicks   

Dylan Wolf - Tuesday, April 17, 2007 2:38:19 AM

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.

Mike - Tuesday, April 17, 2007 3:20:36 PM

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.

Dylan - Wednesday, April 18, 2007 2:40:58 PM

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

Mike - Wednesday, April 18, 2007 8:44:57 PM

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 - Tuesday, May 08, 2007 2:39:40 PM

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 - Saturday, July 21, 2007 3:54:39 PM

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

Mike - Saturday, July 21, 2007 8:53:29 PM

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.

Chris Poind - Wednesday, August 08, 2007 1:08:39 AM

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!

Chris Poind - Wednesday, August 08, 2007 1:09:20 AM

Nice article btw.

Mike - Wednesday, August 08, 2007 2:53:05 AM

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 - Tuesday, August 21, 2007 8:38:31 PM

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 - Tuesday, August 21, 2007 9:41:57 PM

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

Mike - Wednesday, August 22, 2007 1:07:19 PM

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 - Thursday, August 23, 2007 3:07:50 PM

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 - Thursday, August 23, 2007 3:16:25 PM

"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 - Friday, September 14, 2007 7:27:42 AM

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 - Thursday, September 20, 2007 9:11:59 PM

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

Mukesh - Tuesday, May 27, 2008 3:34:07 PM

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

Binu Thayamkery - Tuesday, April 07, 2009 2:27:50 PM

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 - Wednesday, May 20, 2009 9:53:08 AM

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?

Mike - Wednesday, May 20, 2009 11:58:32 AM

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 - Wednesday, May 20, 2009 4:05:18 PM

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 - Wednesday, February 10, 2010 11:59:19 PM

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

Rob - Friday, June 25, 2010 8:43:48 AM

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

Leave a comment



Your name:
 

Your email (not shown):
 
Will display your Gravatar image.

Your website (optional):



About Michael

Michael C. Neel, born 1976 in Houston, TX and now live in Knoxvile, TN. Software developer, currently .Net focused. Board member of ETNUG and organizes CodeStock, East Tennessee's annual developers conference. .Net speaker, a Microsoft ASP.NET MVP and ASPInsider. Co-Founder of FuncWorks, LLC and GameMarx.

Proud father of two amazing girls, Rachel and Hannah, and loving husband to Cicelie who inflates and pops his ego as necessary.

 Subscribe to ViNull.com |  Comments

Follow me on Twitter | Contact Me

Related Posts

ASP.NET: Sorting a GridView with custom objects

This article builds upon the code started in GridView and ObjectDataSource with custom objects. An ObjectDataSource really only works with two types of ... Read more

ASP.NET: Totaling a GridView part 2, the SQL

Not long ago, I wrote a quick guide on adding a total to a GridView.  It was based upon building a cumulative total while looping though the GridView ... Read more

ASP.NET: Adding a total to gridview

One difference between learning the framework and using the framework is rarely anything ever done the way you learned it. The GridView control is a perfect ... Read more

ASP.NET: Cannot use a leading .. to exit above the top directory

This is a really quick post, mostly for myself so the next time I have this issue I can find the answer (yes, I often search my blog before google). The ... Read more

ASP.NET: Creating a UserControl with Child Content

I love ASP.NET User Controls, aka “ascx” files.  These little guys are great for reusable content and dividing up the components of a website.  ... Read more

XNA 3D Primer by Michael C. Neel

XNA 3D Primer by Michael C. Neel
Buy Now: [ Amazon ] [ Wrox ]

GameMarx

CodeStock

ASPInsiders Member

ETNUG Member