Home > Software engineering >  ASP.NET C# Gridview throwing Index out of range
ASP.NET C# Gridview throwing Index out of range

Time:06-23

Im working on a user control that shows available stores from an SQL table on a gridview for the user to select and fill up a textbox. Currently im able to databind everything correctly to the gridview and run actions with the select button inside each row. My current problem is that i can't get to return the cell value into a variable when using "rows[index]". When i reach this i get an Index is out of range error even if my index value is correct while debugging. For experiment purpose i gave the index a manual value of 0 and 1 on different iterations and i still get the error as if my gridview didn't contain any rows.

My method to select the row is using a Gridview with an OnRowCommand triggered by a ButtonField.

Looking at some other similar questions and answers i found that most of the problems where around people getting negative values or not using DataKeyNames but this is not my case. I still can't fix this error

My gridview code is this:

<asp:GridView ID="gvStore" runat="server" AutoGenerateColumns="False" Visible="false" DataKeyNames="Name" OnRowCommand="gvExecSelectSTR_RowCommand">
            <Columns>            
                <asp:BoundField DataField="Name" HeaderText="Name" />            
                <asp:ButtonField ButtonType="Link" Text="Select" CommandName="SelectStore"/> 
            </Columns>
        </asp:GridView>

My button action

protected void gvExecSelectSTR_RowCommand(object sender, GridViewCommandEventArgs e)
        {
            if (e.CommandName == "SelectStore")
            {
                int index = Convert.ToInt32(e.CommandArgument); //Up to this point i get correct row number for index
                GridViewRow row = gvStore.Rows[index]; //This row triggers my error
                IDStore.Text = row.Cells[0].Text;
            }
        }

Additionally, this is the method im using to databind everything to my GridView

private void GridViewStore()
        {
            try
            {
                DataTable StoreTable = new DataTable();
                StoreTable.Columns.AddRange(new DataColumn[1]
                {
                    new DataColumn("Name",typeof(string))                    
                });

                //Can't show internal SQL commands for this but here i read and load
 the SQLscript that selects the table and load it into the reader variable "readerStore"//

                if (readerStore.HasRows)
                {
                    while (readerStore.Read())
                    {  
                        StoreTable.Rows.Add(readerStore["Name"].ToString());                            
                    }
                }
                gvStore.DataSource = StoreTable;
                gvStore.DataBind();
                readerStore.Close();
            }
            catch (Exception ex)
            {
                one rrorOccurred(new ErrorOccurredEventArgs("Grid View Bind Message"   ex.Message, ex));
            }
        }

Is there anything im setting up wrong?

CodePudding user response:

Have you tried instead of

 GridViewRow row = gvStore.Rows[index];

Doing this

 GridViewRow row = (GridViewRow)gvStore.Rows[e.RowIndex];  

CodePudding user response:

why bother with row command? You really don't need that event model, and in most cases, it just causes EXTRA pain.

And if you have a row PK id that you need on button click? Then use datakeys. Datakeys is VERY nice for several reasons.

For tops on the list, it always a HUGE security hole to expose data base PK's to the client side. (the markup can be messed with, and thus the database ID you use now could be anything that was changed in the markup.

So, simple grid view say like this:

        <asp:GridView ID="GHotels" runat="server" CssClass="table" AutoGenerateColumns="false"
            width="50%" DataKeyNames="ID" >
            <Columns>
                <asp:BoundField DataField="FirstName" HeaderText="FirstName"  />
                <asp:BoundField DataField="LastName" HeaderText="LastName"    />
                <asp:BoundField DataField="HotelName" HeaderText="HotelName"  />
                <asp:BoundField DataField="Description" HeaderText="Description" ItemStyle-Width="270" />

                <asp:TemplateField HeaderText = "View" ItemStyle-HorizontalAlign ="Center" >
                    <ItemTemplate>
                    <asp:Button ID="cmdRow" runat="server" Text="View" 
                       CssClass="btn"  />
                    </ItemTemplate>
                </asp:TemplateField>
            </Columns>
        </asp:GridView>

Note the data keys setting in above "ID" is our row PK id, and VERY nice is we do not have, display, show the PK row id in the markup at ANY time!!!

And code to fill then is this:

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
            LoadData();
    }

    void LoadData()
    {
        using (SqlConnection conn = new SqlConnection(Properties.Settings.Default.TEST4))
        {
            string strSQL = 
                "SELECT * FROM tblHotelsA ORDER BY HotelName";
            using (SqlCommand cmdSQL = new SqlCommand(strSQL, conn))
            {
                DataTable rstData = new DataTable();
                conn.Open();
                rstData.Load(cmdSQL.ExecuteReader());
                GHotels.DataSource = rstData;
                GHotels.DataBind();
            }
        }
    }

And we now have this:

enter image description here

Now, we need a click event for the button. For most buttons, you can just double click on the button to add/wire up a event. However, since the button is inside of the GV, then we can't do that. But we can still add the event by markup.

So, in the markup type in onclick="" when you do this, intel-sense will pop up the option to create the event. Say like this:

enter image description here

(under the quotes, hit ctrl-space to pop that intel-sense dialog).

Ok, choose create new event.

Now, flip to code behind, there should be a simple click event for that button.

So, with this code:

    protected void cmdRow_Click(object sender, EventArgs e)
    {
        Button btn = sender as Button;
        GridViewRow gRow = btn.NamingContainer as GridViewRow;

        Debug.Print("Row index click = "   gRow.RowIndex);
        Debug.Print("ID (data keys) = "   GHotels.DataKeys[gRow.RowIndex]["ID"].ToString());
        Debug.Print("FirstName = "   gRow.Cells[0].Text);
        Debug.Print("Last Name = "   gRow.Cells[1].Text);
    }

Output:

enter image description here

So, with this simple setup:

If I have multiple buttons on the GV, then a get a nice simple SEPERATE click event and code stub. No messy row command (to then check some row command arugment).

I get the row index with ease.

I get the code behind NEVER exposed data base PK id (from datakeys)

I am free to get/grab any cell value from the current row.

  • Related