ASP.NET

GridView inline insert, edit, update and delete in ASP.NET (C#)

ASP.NET · · Published 2026-10-22

The GridView can edit its own rows: one click turns a row's labels into text boxes, another saves the changes. Add a footer row with empty boxes and the same grid also inserts records. This tutorial builds that page against a SQL Server table using ADO.NET with parameters, so you can see every step instead of relying on a data source control.

The table

CREATE TABLE dbo.Contacts
(
    ContactId int IDENTITY(1,1) PRIMARY KEY,
    FullName  nvarchar(100) NOT NULL,
    Email     nvarchar(200) NOT NULL,
    City      nvarchar(80)  NULL
);

INSERT INTO dbo.Contacts (FullName, Email, City) VALUES
(N'Ana Ruiz', N'ana@example.org', N'Seville'),
(N'Tom Becker', N'tom@example.org', N'Hamburg');

Add the connection string to web.config:

<connectionStrings>
  <add name="Db" providerName="System.Data.SqlClient"
       connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=Demo;Integrated Security=True" />
</connectionStrings>

The markup

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Contacts.aspx.cs" Inherits="Demo.Contacts" %>
<!DOCTYPE html>
<html>
<head runat="server"><title>Contacts</title></head>
<body>
<form id="form1" runat="server">
<asp:GridView ID="gvContacts" runat="server" AutoGenerateColumns="false"
    DataKeyNames="ContactId" ShowFooter="true"
    OnRowEditing="gvContacts_RowEditing"
    OnRowCancelingEdit="gvContacts_RowCancelingEdit"
    OnRowUpdating="gvContacts_RowUpdating"
    OnRowDeleting="gvContacts_RowDeleting"
    OnRowCommand="gvContacts_RowCommand"
    EmptyDataText="No contacts yet.">
    <Columns>
        <asp:TemplateField HeaderText="Name">
            <ItemTemplate><%#: Eval("FullName") %></ItemTemplate>
            <EditItemTemplate>
                <asp:TextBox ID="txtName" runat="server" Text='<%# Bind("FullName") %>' MaxLength="100" />
                <asp:RequiredFieldValidator runat="server" ControlToValidate="txtName"
                    ErrorMessage="Required" ValidationGroup="edit" Display="Dynamic" />
            </EditItemTemplate>
            <FooterTemplate>
                <asp:TextBox ID="txtNewName" runat="server" MaxLength="100" />
                <asp:RequiredFieldValidator runat="server" ControlToValidate="txtNewName"
                    ErrorMessage="Required" ValidationGroup="add" Display="Dynamic" />
            </FooterTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="E-mail">
            <ItemTemplate><%#: Eval("Email") %></ItemTemplate>
            <EditItemTemplate>
                <asp:TextBox ID="txtEmail" runat="server" Text='<%# Bind("Email") %>' TextMode="Email" />
            </EditItemTemplate>
            <FooterTemplate>
                <asp:TextBox ID="txtNewEmail" runat="server" TextMode="Email" />
            </FooterTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="City">
            <ItemTemplate><%#: Eval("City") %></ItemTemplate>
            <EditItemTemplate>
                <asp:TextBox ID="txtCity" runat="server" Text='<%# Bind("City") %>' />
            </EditItemTemplate>
            <FooterTemplate>
                <asp:TextBox ID="txtNewCity" runat="server" />
            </FooterTemplate>
        </asp:TemplateField>
        <asp:TemplateField>
            <ItemTemplate>
                <asp:LinkButton runat="server" CommandName="Edit" Text="Edit" CausesValidation="false" />
                <asp:LinkButton runat="server" CommandName="Delete" Text="Delete" CausesValidation="false"
                    OnClientClick="return confirm('Delete this contact?');" />
            </ItemTemplate>
            <EditItemTemplate>
                <asp:LinkButton runat="server" CommandName="Update" Text="Save" ValidationGroup="edit" />
                <asp:LinkButton runat="server" CommandName="Cancel" Text="Cancel" CausesValidation="false" />
            </EditItemTemplate>
            <FooterTemplate>
                <asp:LinkButton runat="server" CommandName="AddNew" Text="Add" ValidationGroup="add" />
            </FooterTemplate>
        </asp:TemplateField>
    </Columns>
</asp:GridView>
<asp:Label ID="lblMessage" runat="server" />
</form>
</body>
</html>

Key points in the markup:

The code-behind

using System;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace Demo
{
    public partial class Contacts : Page
    {
        private static string ConnStr =>
            ConfigurationManager.ConnectionStrings["Db"].ConnectionString;

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

        private void BindGrid()
        {
            var table = new DataTable();
            using (var con = new SqlConnection(ConnStr))
            using (var da = new SqlDataAdapter(
                "SELECT ContactId, FullName, Email, City FROM dbo.Contacts ORDER BY FullName", con))
            {
                da.Fill(table);
            }
            gvContacts.DataSource = table;
            gvContacts.DataBind();
        }

        protected void gvContacts_RowEditing(object sender, GridViewEditEventArgs e)
        {
            gvContacts.EditIndex = e.NewEditIndex;
            BindGrid();
        }

        protected void gvContacts_RowCancelingEdit(object sender, GridViewCancelEditEventArgs e)
        {
            gvContacts.EditIndex = -1;
            BindGrid();
        }

        protected void gvContacts_RowUpdating(object sender, GridViewUpdateEventArgs e)
        {
            int id = (int)gvContacts.DataKeys[e.RowIndex].Value;
            GridViewRow row = gvContacts.Rows[e.RowIndex];
            string name = ((TextBox)row.FindControl("txtName")).Text.Trim();
            string email = ((TextBox)row.FindControl("txtEmail")).Text.Trim();
            string city = ((TextBox)row.FindControl("txtCity")).Text.Trim();

            Execute("UPDATE dbo.Contacts SET FullName = @name, Email = @email, City = @city WHERE ContactId = @id",
                cmd =>
                {
                    cmd.Parameters.Add("@name", SqlDbType.NVarChar, 100).Value = name;
                    cmd.Parameters.Add("@email", SqlDbType.NVarChar, 200).Value = email;
                    cmd.Parameters.Add("@city", SqlDbType.NVarChar, 80).Value =
                        city.Length == 0 ? (object)DBNull.Value : city;
                    cmd.Parameters.Add("@id", SqlDbType.Int).Value = id;
                });

            gvContacts.EditIndex = -1;
            BindGrid();
            lblMessage.Text = "Contact updated.";
        }

        protected void gvContacts_RowDeleting(object sender, GridViewDeleteEventArgs e)
        {
            int id = (int)gvContacts.DataKeys[e.RowIndex].Value;
            Execute("DELETE FROM dbo.Contacts WHERE ContactId = @id",
                cmd => cmd.Parameters.Add("@id", SqlDbType.Int).Value = id);
            BindGrid();
            lblMessage.Text = "Contact deleted.";
        }

        protected void gvContacts_RowCommand(object sender, GridViewCommandEventArgs e)
        {
            if (e.CommandName != "AddNew") return;

            GridViewRow footer = gvContacts.FooterRow;
            string name = ((TextBox)footer.FindControl("txtNewName")).Text.Trim();
            string email = ((TextBox)footer.FindControl("txtNewEmail")).Text.Trim();
            string city = ((TextBox)footer.FindControl("txtNewCity")).Text.Trim();

            Execute("INSERT INTO dbo.Contacts (FullName, Email, City) VALUES (@name, @email, @city)",
                cmd =>
                {
                    cmd.Parameters.Add("@name", SqlDbType.NVarChar, 100).Value = name;
                    cmd.Parameters.Add("@email", SqlDbType.NVarChar, 200).Value = email;
                    cmd.Parameters.Add("@city", SqlDbType.NVarChar, 80).Value =
                        city.Length == 0 ? (object)DBNull.Value : city;
                });

            BindGrid();
            lblMessage.Text = "Contact added.";
        }

        private static void Execute(string sql, Action<SqlCommand> addParameters)
        {
            using (var con = new SqlConnection(ConnStr))
            using (var cmd = new SqlCommand(sql, con))
            {
                addParameters(cmd);
                con.Open();
                cmd.ExecuteNonQuery();
            }
        }
    }
}

What happens on each click

  1. Edit: the GridView raises RowEditing with the row index. Setting EditIndex and rebinding renders that row with its EditItemTemplate.
  2. Save: validators in group edit run, then RowUpdating. We read the boxes with FindControl, run the parameterised UPDATE, leave edit mode and rebind.
  3. Cancel: RowCancelingEdit; reset EditIndex to -1.
  4. Delete: the browser's confirm() runs first; returning false cancels the postback. Then RowDeleting deletes by key.
  5. Add: a custom command, so only RowCommand fires. The footer's controls are read through FooterRow.

If you handle RowUpdating or RowDeleting without a data source control, you must handle them; otherwise the GridView throws "The GridView fired event RowEditing which wasn't handled".

The empty-table problem

When the table has no rows, the GridView shows EmptyDataText and does not render the footer, so there is no way to add the first contact. Two common fixes: put the insert form outside the grid (as in the form-based CRUD example), or add an EmptyDataTemplate containing the same input boxes and an Add button, and read them from gvContacts.Controls[0].Controls[0] in RowCommand.

Going further

Official documentation