title: Stop Textbox in GridView Losing Data on Postback
body: <p>I have a GridView which I dynamically add rows to through the button_OnClick event, this adds a new row with the Product name &amp; Product ID from a DropDownList and also contains a column with an empty text box for user input.</p>
  
  <p>My problem is that when I test it and enter data in the text box, then add another Product, the post back causes my data to be lost (the correct number of rows are still there with product names / ids).                                </p>
  
  <pre><code>                            &lt;asp:GridView runat="server" ID="grdSelectedProducts" BorderWidth="1px" CellPadding="3" CellSpacing="2" AutoGenerateColumns="False" OnRowDataBound="grdSelectedProducts_OnRowDataBound" ShowHeaderWhenEmpty="True" DataKeyNames="ProductId"
                                  OnRowCommand="grdSelectedProducts_RowCommand" OnRowDeleted="grdSelectedProducts_RowDeleted" OnRowDeleting="grdSelectedProducts_RowDeleting" EmptyDataText="Please select a Product and click 'Add'" EnableViewState="True"&gt;
                                  &lt;Columns&gt;
                                      &lt;asp:BoundField DataField="Product" HeaderText="Product" ReadOnly="False"/&gt;
                                      &lt;asp:TemplateField HeaderText="Description"&gt;
                                          &lt;ItemTemplate&gt;
                                                   &lt;asp:TextBox runat="server" ID="txtDescriptionEntry" Text="" style="width:98% !important" EnableViewState="True"&gt;&lt;/asp:TextBox&gt;
                                          &lt;/ItemTemplate&gt;
                                      &lt;/asp:TemplateField&gt;
                                      &lt;asp:TemplateField&gt;
                                          &lt;ItemTemplate&gt;
                                              &lt;asp:LinkButton runat="server" ID="linkDelete" runat="server" CommandName="Delete" CommandArgument="&lt;%# Container.DataItemIndex %&gt;"&gt;Remove&lt;/asp:LinkButton&gt;
                                          &lt;/ItemTemplate&gt;
                                      &lt;/asp:TemplateField&gt;
                                      &lt;asp:BoundField DataField="ProductId" HeaderText="ProductId" ReadOnly="False" Visible="False" /&gt;
                                  &lt;/Columns&gt;
                              &lt;/asp:GridView&gt;
  </code></pre>
  
  <p>How can I avoid postback scrapping the data on each txtDescriptionEntry that is created?  There could be between 0 and an infinite number of these text boxes, so I won't have exact names at any one time.</p>
  
  <p><strong>Edit</strong>, as per a comment below I'm including the code for how I add rows to the grid:
          private DataTable ProductDataTable
          {
              get {return ViewState["ProductDataTable"] as DataTable ?? new DataTable(); }
              set { ViewState["ProductDataTable"] = value; }
          }</p>
  
  <pre><code>    private DataTable CreateDataTable(bool isAddingValue, string selectedProduct, string selectedId)
      {
          // if isAddingValue is FALSE then it isn't from a button click to add a Product, it is just
          // a call to create the datatable
  
          DataTable dataTable = ProductDataTable;
          if (!dataTable.Columns.Contains("Product"))
          {
              dataTable.Columns.Add("Product");
              dataTable.Columns.Add("Description"); // This column is free format text that the user enters.
              dataTable.Columns.Add("ProductId");
          }
  
          if (isAddingValue)
          {
              // Get the data from ViewState
              //dataTable = ProductDataTable;
              DataRow dataRow;
              dataRow = dataTable.NewRow();
              dataRow["Product"] = selectedProduct;
              dataRow["ProductId"] = selectedId;
              dataTable.Rows.Add(dataRow);
          }
          else
          {
              grdSelectedProducts.DataSource = null;
              grdSelectedProducts.DataSource = ProductDataTable;
              grdSelectedProducts.DataBind();
  
          }
          // Save the data back to ViewState
          ProductDataTable = dataTable;
  
          return dataTable;
      }
  
      protected void btnAddProduct_OnClick(object sender, EventArgs e)
      {
          string selectedProduct = ddlProduct.SelectedItem.Text;
          string selectedId = ddlProduct.SelectedValue;
  
          DataTable dataTable = CreateDataTable(true, selectedProduct, selectedId);
  
          grdSelectedProducts.DataSource = dataTable;
          grdSelectedProducts.DataBind();
  
      }
  
      protected void grdSelectedProducts_RowCommand(object sender, GridViewCommandEventArgs e)
      {
          if (e.CommandName == "Delete")
          {
              if (!string.IsNullOrEmpty(e.CommandArgument.ToString()))
              {
                  int rowIndex = Convert.ToInt32(e.CommandArgument);
                  DataTable table = CreateDataTable(false, string.Empty, string.Empty);
                  table.Rows.RemoveAt(rowIndex);
                  grdSelectedProducts.DataSource = table;
                  grdSelectedProducts.DataBind();
              }
          }
      }
  </code></pre>
  
  <p>And in the Page_Load event, if it isn't a PostBack there is also binding of an empty list</p>
  
  <pre><code>        grdSelectedProducts.DataSource = new List&lt;Products&gt;();
          grdSelectedProducts.DataBind();
  </code></pre>