Getting your data out of the data controls

After you've created a layout with a data control of your choice and eventually bound them to some datasource, you almost always want to get that data back out of them(and why wouldn't you, its your data). One thing you'll learn about developing asp.net webpages is that its like petting a porcupine (yikes). If you go with the flow, you probably won't get stuck, but the moment you try to go against the grain you end up with a hand full of thorns.

Most of the data controls have events ending in ing and ed e.g. RowUpdating, RowUpdated etc. In most of the event handlers of the ing events it is easy to get the values from the event args.

private void OnGridViewRowUpdating(object sender, GridViewUpdateEventArgs e) {

    IDictionary keys = e.Keys;

    IDictionary newValues = e.NewValues;

    IDictionary oldValues = e.OldValues;

}

Forgive me for the variation in my code formatting I'm trying to find the right one.

If we look at this event we can see the GridView nicely packages for us the new values, old values and the keys for the updating row. Unfortunately these dictionaries are only filled out if you are bound to a datasource control :(. So that means when you bind to some raw collection and hook up to the DataSource property then call databind, if you try to handle the delete or update events these dictionaries are going to be empty. Right now you must be asking yourself how can you get those dictionaries filled out just as if you were bound to a Datasource control? The good news is you CAN do it(this is what this blog post is all about right?).

Going Hunting in the Control Tree



This is probably one of the worst things you can do. From the time you write code that depends on the immediate layout of your page then your asking for trouble. I often see people on the forums writing code like this:

((TextBox)GridView1.Rows[e.RowIndex].Cells[2].Controls[1]).Text

Seeing stuff like that makes my spine tingle (and not in a good way). DO NOT write code like this!

FindControl
FindControl is a very powerful method on Control that allows you to search for a nested control, NOT synonymous to DOM function getElementById. I Often see people abusing find control and not understand that it is NOT recursive by default and complain when code like this

((TextBox)GridView1.FindControl("TextBox1")).Text

Throws a null reference exception. My advice is use Use FindControl as a last resort.

2-way DataBinding
2-way Databinding is a cool feature in ASP.NET 2.0 which allows the user to write some special syntax to bind against property values they would like to extract from a control.
There is alot of magic going on behind the scenes (which I will blog about in a separate post), but you do not need to know how it works to use it, e.g

<asp:GridView runat="server" ID="GridView1">

    <Columns>

        <asp:TemplateField>

            <EditItemTemplate>

                <asp:TextBox ID="TextBox1" runat="server" Text='<%# Bind("ProductName") %>'></asp:TextBox>

            </EditItemTemplate>

        </asp:TemplateField>

    </Columns>

</asp:GridView>

When you write the <%# Bind("ProductName") #> expression then the value of the Text property is pushed into a dictionary which the data control can retrieve later. So how does GridView/FormView/DetailsView/ListView get these values? Each data control has a method which is responsible for populating a dictionary of name value pairs from field name to value. Here is a mapping of data control to method used to extract values:

GridView => protected ExtractRowValues
FormView => protected ExtractRowValues
DetailsView = > protected ExtractRowValues
ListView => public ExtractItemValues

As we can see with the exception of ListView these oh so useful methods are protected, that means we can't call them from our code if we are using these built in controls. What can we do to surface these methods:

  • Derive new controls that expose ExtractRowValues through a public method
  • Use private reflection to call the protected method (yikes)
  • Stick all of your interesting fields in DataKeyNames, then use the DataKeys[rowIndex].Values[fieldName] (and watch your ViewState grow :( )
  • Do Nothing :)

To rectify some of this I've written a little helper for the GridView (and the other controls if you demand) that basically duplicates the functionality of ExtractRowValues method.

public static IDictionary GetValues(GridViewRow row) {           

    IOrderedDictionary values = new OrderedDictionary();

    foreach (DataControlFieldCell cell in row.Cells) {

        if (cell.Visible) {

            // Extract values from the cell

            cell.ContainingField.ExtractValuesFromCell(values, cell, row.RowState, true);

        }

    }

 

    return values;

}

The method itself is pretty simple. It iterates over the control collection of the GridViewRow and calls ExtractValuesFromCell on each cell which puts values into the dictionary.

Uses
Most data controls support writing custom commands and handling some Command event(RowCommand for GridView). This method would come in handy if you needed to get the values out of the GridView for some custom command you wanted to execute.

If you can, use 2 way databinding, if your control doesn't have a useful property to bind against and you need to do some more logic to get the right value then use FindControl.

Edit:
I've updated the code to loop over the Cells collection instead of the Controls collection (which is alot cleaner). Also there is no real compelling reason to return a strongly typed IDictionary<string, object>, so I just return the IDictionary instead.

DetailsView:

Reader daveh551 needed some help adapting the code to DetailsView so here it is:

public static IDictionary GetValues(DetailsView detailsView) {
    IOrderedDictionary values = new OrderedDictionary();
    foreach (DetailsViewRow row in detailsView.Rows) {
        // Only look at Data Rows
        if (row.RowType != DataControlRowType.DataRow) {
            continue;
        }
        // Assume the first cell is a header cell
        DataControlFieldCell dataCell = (DataControlFieldCell)row.Cells[0];                
        // If we are showing the header for this row then the data is in the adjacent cell
        if (dataCell.ContainingField.ShowHeader) {
            dataCell = (DataControlFieldCell)row.Cells[1];
        }
 
        dataCell.ContainingField.ExtractValuesFromCell(values, dataCell, row.RowState, true);
    }
    return values;
}

FormView:

public static IDictionary GetValues(FormView formView) {
    IOrderedDictionary fieldValues = new OrderedDictionary();
    ExtractValuesFromBindableControls(fieldValues, formView);
    IBindableTemplate itemTemplate = null;
 
    if (formView.CurrentMode == FormViewMode.ReadOnly && formView.ItemTemplate != null) {
        itemTemplate = formView.ItemTemplate as IBindableTemplate;
    }
    else if (formView.CurrentMode == FormViewMode.Edit && formView.EditItemTemplate != null) {
        itemTemplate = formView.EditItemTemplate as IBindableTemplate;
    }
    else if (formView.CurrentMode == FormViewMode.Insert && formView.InsertItemTemplate != null) {
        itemTemplate = formView.InsertItemTemplate as IBindableTemplate;
    }
    if (itemTemplate != null) {
        foreach (DictionaryEntry entry in itemTemplate.ExtractValues(formView)) {
            fieldValues[entry.Key] = entry.Value;
        }
    }
    return fieldValues;
}
 
private static void ExtractValuesFromBindableControls(IOrderedDictionary values, Control container) {
    IBindableControl control = container as IBindableControl;
    if (control != null) {
        control.ExtractValues(values);
    }
    foreach (Control childControl in container.Controls) {
        ExtractValuesFromBindableControls(values, childControl);
    }
} 

Let me know if you find any bugs.
Hope this helps

UPDATE: I've had some requests to add more data controls and to convert the code to VB etc. So I decided to instead put the code in an assembly and make available for download here.

32 Comments

  • >> ((TextBox)GridView1.Rows[e.RowIndex].Cells[2].Controls[1]).Text

    >> DO NOT write code like this!

    I couldn't agree more.

  • Your right, thanks for correcting me :)

  • Nice !!!

  • nice blog keep it up.

  • ((TextBox)GridView1.Rows[e.RowIndex].Cells[2].Controls[1]).Text
    is not good, that I agree.
    What to do when using asp:BoundField?
    Any other alternatives? [:)]

  • Hi naveenj, you can use the same code for BoundField. ExtractValuesFromCell is an abstract method on DataControlField and each implementation (BoundField, TemplateField etc.) each have a different implementation.

  • My current issue deals with extracting a non text value from a gridview row being selected. (suppose I have two fields with a bytestream/array containing a Wav file and bmp picture (SoundByte, ImageByte) respectively. I have included them as fields in my Gridview control.

    dim datarow as gridview1.selectedrow


    ' This is the command I need...

    dim Soundbytes() as byte = datarow("SoundByte")
    dim photobytes() as byte = datarow("ImageByte")

    me.textbox1.text = soundbytes.length & " Bytes retrieved from database ..."

    Can you give me the proper syntax / method to retrieve this info into a byte array so I can use it in my code? It only seems to support the retrieval of Text.

    Perhaps what I need is to be able to get the data from the datasource based on the item selected. Any help is much appreciated.

  • Hi Ed, in that case I think you'd need to serialize the bytes into a hidden field. Then when you want to retrieve it, deserialize the string into bytes. I can try to show you a sample if you'd like.

  • How about DataList control ? Can you show me how to get some data from DataList ? I have the following scenario :

    Col1 | Col2 | Col3

    V1 CH1 1
    V1 CH1 2
    V1 CH2 3

    This DataList data source 's bound to a function that returns IQueryable (LINQ. It works fine.

    Anyway, I just want to get the a value from the Col1. Thanks.

  • @ bayonian

    This won't work for DataList because it doesn't have this kind of abstraction. DataList is one of the older asp.net 1.1 controls. Also DataList and Repeater don't have this mechanism so your stuck with FindControl :(.

  • @Kay Yeah that is pretty bad. I'll see what I can do about this.

    Thanks for the heads up.

  • David,

    A little help please.
    I'm trying to adapt this for the DetailsView, which has Fields, not Cells. Here's the code I've ended up with:
    public static IDictionary GetValues(DetailsView dv)
    {
    IOrderedDictionary values = new OrderedDictionary();
    DataControlRowState state;
    if (dv.CurrentMode == DetailsViewMode.Edit)
    state = DataControlRowState.Edit;
    else if (dv.CurrentMode == DetailsViewMode.Insert)
    state = DataControlRowState.Insert;
    else state = DataControlRowState.Normal;
    foreach (DataControlField field in dv.Fields)
    {
    if (field.Visible)
    {
    // Extract values from the cell
    field.ExtractValuesFromCell(values, ??? , state, true);
    }
    }
    return values;
    }

    I can't figure out what to put in for the cell, where I have ???, since it wants a DataControlFieldCell, and what I have is a DataControlField. I'm not exactly clear on the what the relation between a DataControlFieldCell and a DataControlField is, other than the cell is contained in the field. But I don't know how to find or create the cell in this case.

    Can you help?

  • Hi daveh551,
    DetailsView is a bit more complicated than GridView but I've updated the post with code that works with DetailsView. Hope this helps you out.

  • Can u plz make an example of a listview? Thx

  • Hi dear author,
    I came here following the discussion on the asp.net forum: http://forums.asp.net/t/1362718.aspx

    I have always been using FindControl() method: much easier and handy. You need not to write (and call everytime) the functions to extract values (or cells) using ExtractValuesFromCell.

    This ExtractValuesFromCell way of extracting values from cell is just another way around it. So what's wrong to go with FindControl. Please give your opinion (perhaps you could update the post to add a sections: Why to not use FindControl(); I am requesting this because the argument among developers would go longer!).

    Hey, this could be the advantage: People sometimes ask how to get all the values from my gridview since I have been editing and updating it, so changing the original value from the datasource. In such case this trick could help a lot.

    Thanks.

  • I'm still not getting to read the new updates values of the editing row. Whats missing?

  • What are you trying to do?

  • David,

    I've been looking for a way to extract Insert and Update data from the DetailsView; I hope that your code is the solution. I have read and re-read the code that you provided for the DetailsView, bit alas I'm a C# dullard. Could you please translate for your VB readers? My ASP application uses several small CSV files. I wrote a class that extracts a record from the CSV file, loads it into a List(of Members) and binds the list to the DetailsView. It is all working except my ability to grab the Insert & Update data from the DetailsView.

  • I'm trying to use the DetailsView version (translated to VB), but the values collection remains empty.

    Right now I'm calling GetValues in OnItemUpdating to capture the new values to be able to update my database.
    Isn't that the place to do this?

    My GetValues code:

    Public Function GetValues() As IOrderedDictionary
    Dim l_aValues As IOrderedDictionary = New OrderedDictionary()
    For Each l_oRow As DetailsViewRow In Me.Rows
    ' only look at DataRow
    If l_oRow.RowType = DataControlRowType.DataRow Then
    Dim l_oCell As DataControlFieldCell = CType(l_oRow.Cells(0), DataControlFieldCell)
    If l_oCell.ContainingField.ShowHeader Then
    ' if we are showing the header for this row then the data is in the adjacent cell
    l_oCell = CType(l_oRow.Cells(1), DataControlFieldCell)
    End If
    l_oCell.ContainingField.ExtractValuesFromCell(l_aValues, l_oCell, l_oRow.RowState, True)
    End If
    Next
    Return l_aValues
    End Function

  • The link for the assembly including the vB version is broken. the link at the end of the article. Can this pleae get fixed?

  • I must say it nice to see some details on this. I have been searching for information on FormView for weeks now and can't find anything helpful.

    Can you develop this coding scheme for FormView and exlain how I can populate textboxes on the Insert Template from drop down boxes that I have added to simplify data entry. The DD are also on the Insert template. I can not seem to get at the data and move into the text boxes.

  • nice... thank you....

  • I tried downloading the assembly via the link, but it appears to be broken.

  • Hi David,

    This is a really interesting way to extract the values from the DataControls. I've been searching for such a thing for a long time.. and im new to Asp.Net programming...

    The thing is, i've got a Business Object which i bind to the GridView.. now wen i extract the values from the GridView in the way u've mentioned i get the values in the IDictionary object. I jus want to know, how should i map these values back to my Business Object ?

  • Hey Ravi,

    If you send me an email I can show you how you can reconstruct your object from the dictionary. You have to use reflection to create the business object and set property values from the dictionary key value pairs.

  • Hi,

    Ya Sure... Can u let me know ur EMail ID ?

  • http://weblogs.asp.net/user/Profile.aspx?UserID=33565

    Send me one from here.
    Thanks

  • Hi David..

    I've sent u the mail... Waiting 4 ur reply...

  • Good stuff for sure, but doesn't anyone find it a little odd that extracting values from a gridview is such a convoluted and painful process?

  • Thanks for this excellent bit of code, just what I needed.

    Had a problem with it to start with, but then I realised it was because I was using Eval("ProductName") in my DetailsView, instead of Bind("ProductName"). Once I'd fixed that, it worked brilliantly.

  • I am reading the cell values of multiple rows using the below code. I get the updated value (for example quantity column) correctly only for the first row.For second row 'quantity' column value I get the old value even though the 'quantity' column value changed from 1 to 10. ( ie I get the value of 1 instead of 10 for the second row) , can you please tell where I am going wrong ?

    -----------------------------------------------

    public static IDictionary GetValues(GridViewRow row) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;

    &nbsp; &nbsp;IOrderedDictionary values = new OrderedDictionary();

    &nbsp; &nbsp;foreach (DataControlFieldCell cell in row.Cells) {

    &nbsp; &nbsp; &nbsp; &nbsp;if (cell.Visible) {

    &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// Extract values from the cell

    &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;cell.ContainingField.ExtractValuesFromCell(values, cell, row.RowState, true);

    &nbsp; &nbsp; &nbsp; &nbsp;}

    &nbsp; &nbsp;}

    &nbsp; &nbsp;return values;

    }

    Thanks

    S

  • the code download link has broken

Comments have been disabled for this content.