GridView inline insert, edit, update and delete in ASP.NET (C#)
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:
DataKeyNames="ContactId"keeps each row's primary key on the server, so you never need a hidden column for it.- The
CommandNamevaluesEdit,Update,CancelandDeleteare recognised by the GridView and raise the matching events.AddNewis our own name, so it only raisesRowCommand. <%#: … %>HTML-encodes the value (available since .NET 4.5), protecting the page against script stored in the data.- Separate validation groups stop the footer's validators from blocking a row update and vice versa.
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
- Edit: the GridView raises
RowEditingwith the row index. SettingEditIndexand rebinding renders that row with itsEditItemTemplate. - Save: validators in group
editrun, thenRowUpdating. We read the boxes withFindControl, run the parameterisedUPDATE, leave edit mode and rebind. - Cancel:
RowCancelingEdit; resetEditIndexto -1. - Delete: the browser's
confirm()runs first; returningfalsecancels the postback. ThenRowDeletingdeletes by key. - Add: a custom command, so only
RowCommandfires. The footer's controls are read throughFooterRow.
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
- Paging: set
AllowPaging="true", handlePageIndexChanging, setPageIndexand rebind. Keeping check boxes across pages is covered in this article. - Concurrency: include a
rowversioncolumn in theWHEREclause of the update to detect edits made by someone else meanwhile. - Exporting the grid: export to Word, Excel, text or PDF.
Official documentation
- Microsoft Learn: GridView class, GridView.RowUpdating, GridView.DataKeyNames
- Microsoft Learn: An overview of inserting, updating and deleting data (Web Forms data access tutorials)
- Microsoft Learn: ADO.NET overview and SqlCommand.ExecuteNonQuery