Pierre Greborio.NET

Talking about .NET world

Playing with DataGrid and ViewState

Some days ago I talked about the problem of the ViewState with ASP.NET list controls. Several readers (thanks for your feedback) suggested some solutions. Today I would show, pragmaticly, how the problem could be solved. Obviously, this is my solution to the problem, NOT "the" solution to the problem ;-)

Well, consider a simple Datagrid containing two columns: the first one contains a DrillDownList and the second one a TextBox. Finally, you have a button used to add new rows. You want also have all rows updatable in any moment.

Here is the ASP.NET code:

<asp:datagrid id="dgScores" runat="server" AutoGenerateColumns="false">
  <Columns>
    <asp:TemplateColumn HeaderText="Country">
      <ItemTemplate>
        <asp:DropDownList ID="ddlCountry" Runat="server"></asp:DropDownList>
      </ItemTemplate>
    </asp:TemplateColumn>
    <asp:TemplateColumn HeaderText="Score">
      <ItemTemplate>
        <asp:TextBox ID="txtScore" Runat=server Text='<%# DataBinder.Eval(Container.DataItem, "Score") %>'>
        </asp:TextBox>
      </ItemTemplate>
    </asp:TemplateColumn>
    <asp:TemplateColumn HeaderText="Add">
      <HeaderTemplate>
         <asp:LinkButton ID="bntAdd" CommandName="ADD" Runat="server">Add</asp:LinkButton>
      </HeaderTemplate>
    </asp:TemplateColumn>
  </Columns>
</asp:datagrid>

Whereas the code-behind is as following:

public class DataGridDemo : System.Web.UI.Page
{
 protected System.Web.UI.WebControls.DataGrid dgScores;

 ArrayList _data;

 private void Page_Load(object sender, System.EventArgs e)
 {
  if(Session["LIST"] == null)
   Session["LIST"] = MakeDS();
  _data = Session["LIST"] as ArrayList;

  if(!IsPostBack)
   Bind();
 }

 private void Bind()
 {
  dgScores.DataSource = _data;
  dgScores.DataBind();
 }

 private ArrayList MakeDS()
 {
  ArrayList al = new ArrayList();
  al.Add(new Data(0, 0));
  return al;
 }

 private DataTable FillDDL()
 {
  DataTable dt = new DataTable();
  dt.Columns.Add("ItemID", typeof(int));
  dt.Columns.Add("Item", typeof(string));

  for(int i = 0; i < 100; i++)
   dt.Rows.Add(new object[2] {i, string.Concat("Item ", i.ToString())});

  return dt;
 }

 #region Web Form Designer generated code
 override protected void OnInit(EventArgs e)
 {
  InitializeComponent();
  base.OnInit(e);
 }
 
 private void InitializeComponent()
 {   
  this.dgScores.ItemCommand += new System.Web.UI.WebControls.DataGridCommandEventHandler(this.DataGrid1_ItemCommand);
  this.dgScores.ItemDataBound += new System.Web.UI.WebControls.DataGridItemEventHandler(this.DataGrid1_ItemDataBound);
  this.Unload +=new EventHandler(DataGridDemo_Unload);
  this.Load += new System.EventHandler(this.Page_Load);

 }
 #endregion

 private void DataGrid1_ItemDataBound(object sender, System.Web.UI.WebControls.DataGridItemEventArgs e)
 {
  if(e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
  {
   DropDownList ddl = e.Item.FindControl("ddlCountry") as DropDownList;
   ddl.DataSource = FillDDL();
   ddl.DataTextField = "Item";
   ddl.DataValueField = "ItemID";
   ddl.SelectedValue = (_data[e.Item.ItemIndex] as Data).CountryID.ToString();
   ddl.DataBind();
  }
 }

 private void DataGrid1_ItemCommand(object source, System.Web.UI.WebControls.DataGridCommandEventArgs e)
 {
  if(e.CommandName.Equals("ADD"))
  {
   _data.Add(new Data(0, 0));
   RecordChanges();
   Bind();
  }
 }

 private void RecordChanges()
 {
  for(int i = 0; i < dgScores.Items.Count; i++)
  {
   Data data = _data[i] as Data;
   data.CountryID = int.Parse((dgScores.Items[i].Cells[0].FindControl("ddlCountry") as DropDownList).SelectedValue);
   data.Score = int.Parse((dgScores.Items[i].Cells[0].FindControl("txtScore") as TextBox).Text);
  }
 }

 private void DataGridDemo_Unload(object sender, EventArgs e)
 {
  Session["LIST"] = _data;
 }
}

public class Data
{
 private int _countryID;
 private int _score;

 public Data()
 {}

 public Data(int countryID, int score)
 {
  _countryID = countryID;
  _score = score;
 }

 public int CountryID
 {
  get { return _countryID; }
  set { _countryID = value; }
 }

 public int Score
 {
  get { return _score; }
  set { _score = value; }
 }
}

The above code works very fine, from a functional point of view, but probably it is the worst one from a technical point of view. First of all, the items of the DropDownList are created each time (consider also other scenarios, where you get the data from a database). This is very resource expansive. A better alternative is to cache the DataTable content, then FillDDL could become:

private DataTable FillDDL()
{
 DataTable dt;

 if(Cache["ITEMS"] == null)
 {
  dt = new DataTable();
  dt.Columns.Add("ItemID", typeof(int));
  dt.Columns.Add("Item", typeof(string));

  for(int i = 0; i < 100; i++)
   dt.Rows.Add(new object[2] {i, string.Concat("Item ", i.ToString())});

  Cache["ITEMS"] = dt;
 }
 else
  dt = Cache["ITEMS"] as DataTable;

 return dt;
}

Data caching is the best solution since you have just one list in memory even if you use it several times. From the server side point of view, probably you found the best compromise between complexity and performance.

There is another problem. The DataGrid (as all other ASP.NET Web Controls) enable by default the ViewState. This means that we have all the DataGrid content into the page and that content, obviously, is carried to and from the server (stressing your bandwidth). Consider the sample above. With one record, the viewstate is 1.7Kb, with two records 3.5Kb, with three records 5Kb and so on.

A possible solution to this exponetial resources consumption is to disable the ViewState to the DropDownList. Doing that, you loose some benefits, such as the proprties state of the control, such as SelectedValue. Then , in order to get the selected value, you have to query the posted data. Then, the code:

data.CountryID = int.Parse((dgScores.Items[i].Cells[0].FindControl("ddlCountry") as DropDownList).SelectedValue);

Must be modified as:

data.CountryID = int.Parse(Request.Form[string.Concat(dgScores.Items[i].UniqueID, ":ddlCountry")]);

In that way we get a real advantage with the ViewState size: with 1 record 240 bytes, with 2 records 245 bytes, with 3 records 250 bytes and so on. Can you do better ? Obviously, yes ! But this will be another subject...I hope.

Posted: Aug 26 2003, 12:25 PM by PierreG | with 1 comment(s) |
Filed under:

Comments

jbh said:

jhb
# July 20, 2004 11:08 AM