Pierre Greborio.NET

Talking about .NET world

August 2003 - Posts

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 no comments |
Filed under:
Encoding support for web services

ASP.NET web services implementation lacks in the support for encoding. The default encoding is UTF-8 and there is only one way for changing that value: through a SoapExtension. Then, I developed a new class EncodingExtension which replace the default UTF-8 encoding to the encoding defined into the attribute. The source code is available on GotDotNet.

I hope to write one story in the near future...I just need  some (free) time :-)

Posted: Aug 23 2003, 12:18 AM by PierreG | with 1 comment(s)
Filed under:
SoapExtension configuration

Today I had an interesting chat with Christian Weyer about SoapExtension configuration. My question was pretty simple: "I configured my SoapExtension both on web.config and as WebMetod attribute. Why ProcessMessage is executed twice for every SoapMessageStage ?"

When a web service is executed the first time, ASP.NET loads all the extensions both from the web.config (see section soapExtensionTypes) and the extension attribute (inhertoed from SoapExtensionAttribute) applied to the WebMethod and then process all of them. The scope of the extensions configured into the web.config is global over all WebMethods of the web application, whereas the scope of the attribute is local to the single WebMethod.

If you configure the same SoapExtension both on web.config and decorating the WebMethod with the attribute you will execute the same SoapExtension twice. What changes between these two configuration is the initialization process. When you configure in the web.config, it is called object GetInitializer(Type) where the Type is the type of the web service currently executed. If you configure the extension only by attribute, object GetInitializer(LogicalMethodInfo, SoapExtensionAttribute) get called, and contains both the information (at metadata level) about the WebMethod called and the SoapExtensionAttribute applied to it. Note that these methods are called only once and then they are cached.

The advantage in configuring a SoapExtension into the web.config is for scoping (consider for example a SOAP message tracing system), you don't have to configure each WebMethod. If you need to set some parameters instead, the attribute way is the right one.

Posted: Aug 21 2003, 06:57 PM by PierreG | with no comments
Filed under:
Extending ASP.NET controls with client-side script code
On the august issue of MSDN Magazine online, Scott Mitchell, published an interesting article on how to extend a simple ASP.NET control with some client code script. Event if the sample is very simple (and maybe not useful for business applications), he opens up a new way on the control's development. Maybe, in the near future, we will find some extensions to ASP.NET controls (DataGrid, DataList, Repeater, ...) that permits to add new records on client side avoiding any round-trip with the server. That could be very useful...I think.
Posted: Aug 19 2003, 02:48 PM by PierreG | with no comments
Filed under:
ViewState or not ?

One of the most interesting new feature of ASP.NET (vs ASP) is the new state management mechanism called ViewState. In few words, the page controls retains their state (if enabled) across post-backs in a hidden field (of the page) called __VIEWSTATE. As this is a content of the page, the state is sent between the server and the client for each request, for this reason it shouldn't be used for large amount of data.

I think we should avoid on using ViewState for another reason too. Considering a simple DropDownList control containing no more than 52 items (ie. US States) and a complex form (data gathering) where you have 10 DropDownList all containing the same content. Then, the ViewState will retain 10 lists with exactly the same content ! Not so good.

I think that this is a case where the old-style technique (ASP) works better...loading the index selected in the POST data.

Posted: Aug 15 2003, 10:13 PM by PierreG | with 6 comment(s)
Filed under:
Horrible spamming !

I just was back from vacation and then I downloaded my e-mails. I had 50% of junk e-mails...this is a terrific phenomenon.

Some of these e-mails are signed by Microsoft and contains an attachement (security patch). Not convinced at all, I checked to the Microsoft web site and got the following link http://www.microsoft.com/technet/treeview/default.asp?url=/technet/security/news/patch_hoax.asp. So, be care on opening anykind of email....

Great DHTML calendar

Developing web sites (now only ASP.NET) it's funny to give, to the navigator, some rich controls in order to simulate some rich-client behavior. This jsCalendar is really fantastic (from my point of view) since it is fully configurable and works fine for a large number of user agents.

More Posts