text: NOTE: I have slightly modified this post and the associated source. Make sure to take a look at v2 after reading this post:
  -Andrew
  >>>
  In ASP.NET version 1.x, I used the footer on the DataGrid control to insert new rows by placing a series of TextBox, DropDownLists, CheckBoxes etc. controls on it and then handling a save button. This also works with the GridView in version 2.0 but with one major exception: if your data source is empty, the GridView will hide your footer (and header) and only display the EmptyDataText.
  Conventional wisdom says that you should use a DetailsView to insert rows, but I don't want to shift the user between two different controls. You can edit and delete rows with a GridView so why not Insert?
  Here is the solution. I am using an ObjectDataSource control to wire up my data access class. If the DataTable coming back from my data class is empty, I add a null row. This works fine but now you get blank rows in the GridView. To handle that, I hide the rows during the RowCreated event. Lastly, I clear the controls off of the now hidden row so that there are no binding errors as would be the case with a cast boolean value which I do to set the value of a CheckBox in my GridView.
  You can download the entire solution and sample database here:
  private bool LoadDataEmpty
  {
  // some controls that are used within a GridView,
  // such as the calendar control, can cuase post backs.
  // we need to preserve LoadDataEmpty across post backs.
  get { return (bool)(ViewState["LoadDataEmpty"] ?? false); }
  set { ViewState["LoadDataEmpty"] = value; }
  }
  protected void ObjectDataSourceMain_Selected(object sender, ObjectDataSourceStatusEventArgs e){ // bubble exceptions before we touch e.ReturnValue if (e.Exception != null) { throw e.Exception; }
  // get the DataTable from the ODS select mothod DataTable dataTable = (DataTable)e.ReturnValue;
  // if rows=0 then add a dummy (null) row and set the LoadDataEmpty flag.
  if (dataTable.Rows.Count == 0)
  {
  dataTable.Rows.Add(dataTable.NewRow());
  LoadDataEmpty = true;
  }
  else
  LoadDataEmpty = false;
  protected void GridViewMain_RowCreated(object sender, GridViewRowEventArgs e)
  // when binding a row, look for a zero row condition based on the flag.
  // if we have zero data rows (but a dummy row), hide the grid view row
  // and clear the controls off of that row so they don't cause binding errors
  if (LoadDataEmpty && e.Row.RowType == DataControlRowType.DataRow)
  e.Row.Visible = false;
  e.Row.Controls.Clear();
  protected void GridViewMain_RowCommand(object sender, GridViewCommandEventArgs e)
  // handle the save button on the footer row. this is the only manual data operation
  // that must be done. update and delete are handled by the gridview and ODS
  if (e.CommandName == "Save")
  string code = ((TextBox)GridViewMain.FooterRow.FindControl("TextBoxCode")).Text;
  string description = ((TextBox)GridViewMain.FooterRow.FindControl("TextBoxDescription")).Text;
  bool isActive = ((CheckBox)GridViewMain.FooterRow.FindControl("CheckBoxIsActive")).Checked;
  new LocationData().Insert(code, description, isActive);
  GridViewMain.DataBind();
  Disclaimer
  The opinions expressed herein are my own personal opinions and do not represent
  my employer's view in any way.