This question is about a very strange behavior of web controls. Consider the following please.
Scenario
I have a web control which is used to show some data. MyWebControl.ascx file is the following:
<%@ Control ViewStateMode="Enabled"
Language="C#"
AutoEventWireup="true"
CodeFile="MyWebControl.ascx.cs"
Inherits="MyWebControl" %>
<div>
<asp:Label ID="MyLabel1" runat="server"></asp:Label>
<asp:Label ID="MyLabel2" runat="server"></asp:Label>
</div>
And code behind MyWebControl.ascx.cs is:
public partial class MyWebControl :
System.Web.UI.UserControl {
protected string str1;
protected string str2;
public string Str1 {
get { return this.str1; }
set { this.str1 = value; }
}
public string Str2 {
get { return this.str2; }
set { this.str2 = value; }
}
protected void Page_Load(object sender, EventArgs e) {
this.MyLabel1.Text = this.str1;
this.MyLabel2.Text = this.str2;
}
}
Well, I use this control inside a ListView of mine in an ordinary web form. This web form is called Default.aspx and, following, I am showing a slice of its code, only the one where the ListView resides in:
<%@ Page Title="My page" Language="C#" AutoEventWireup="true"
CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register TagPrefix="my" TagName="MyWC" Src="~/MyWebControl.ascx" %>
...
...
<div>
<asp:ListView ID="MyLV_ListView" runat="server"
EnableViewState="true"
OnPreRender="MyLV_ListView_PreRender"
OnItemDataBound="MyLV_ListView_ItemDataBound">
<ItemTemplate>
<my:MyWC ID="WC_MyWC" runat="server" />
</ItemTemplate>
</asp:ListView>
</div>
This web form code-behind Default.aspx.cs is:
public partial class _Default : System.Web.UI.Page {
protected void Page_Load(object sender, EventArgs e) {
if (!this.IsPostBack) {
DAO d = new DAO(...); /* This object will contain some data */
/* Data are somehow paged, changing index, we have other data */
/* Please do not worry about paging, it works, problem is another */
d.PageIndex = 0;
this.ViewState["DAO"] = d; /* DAO is serializable */
this.ViewState["PageIndex"] = 0;
Data[] data = d.GetData(); /* Returns an array of objects representing my data */
/* Custom way of storing data in objects */
/* GetData use the PageIndex to get only a range of results */
this.MyLV_ListView.DataSource = data;
this.MyLV_ListView.DataBind();
}
}
protected void MyLV_ListView_ItemDataBound(object sender, ListViewItemEventArgs e) {
if (e.Item.ItemType == ListViewItemType.DataItem) {
((MyWebControl)e.Item.FindControl("WC_MyWC")).Str1 =
((Data)e.Item.DataItem).DataField1;
((MyWebControl)e.Item.FindControl("WC_MyWC")).Str2 =
((Data)e.Item.DataItem).DataField2;
}
}
protected void MyLV_ListView_PreRender(object sender, EventArgs e) {
if (this.IsPostBack)
this.MyLV_ListView.DataBind();
}
// A TreeView will cause the page to reload,
// this is the handler called when a tree node is
// selected, the node is used to change the page
// for showing data in my listview.
protected void MyLVPaging_TreeView_SelectedNodeChanged(object sender, EventArgs e) {
this.ViewState["PageIndex"] = int.Parse(this.MyLVPaging_TreeView.SelectedNode.Value);
((DAO)this.ViewState["DAO"]).PageIndex = (int)this.ViewState["PageIndex"];
// After setting the new index, GetData will return another set of results
this.MyLV_ListView.DataSource = ((DAO)this.ViewState["DAO"]).GetData();
}
}
The problem
What is my problem???
Well, when the page loads for the first time I can see some data in the list view, so everything works.
When I select another page, I cause the page to reload and post back… my ListView will show the correct number of entries but with no data in it. The web control is placed as many times as the number of retrieved data records, but the web control has no data in it.
Debugging results
I conducted some debug sessions and this is what happens, please follow me, this is VERY IMPORTANT to understand I guess.
Page loads for the first time
1) Load page method is executed.
2) ListView‘s ItemDataBound event handler is executed. Well, given that I have three elements in the Data[] array in the DataSource, the handler is called three times. Inspecting values when proceeding debugging the method, I can see that ((Data)e.Item.DataItem).DataField1 and ((Data)e.Item.DataItem).DataField2 are the correct values retrieved from the bound data source in the ListView.
After the method is executed, the debugger cursor moves to MyWebControl.ascx.cs to the control’s Page_Load method. I can see, inspecting variables, that this happens:
protected void Page_Load(object sender, EventArgs e) {
this.MyLabel1.Text = this.str1; // <-- this.str1 has the correct value loaded in the list view itemdatabound event handler.
this.MyLabel2.Text = this.str2; // <-- this.str2 has the correct value loaded in the list view itemdatabound event handler.
}
3) The ListView PreRender method is called.
4) The page shows and everyting is ok!
Page posts back after selecting a node in the TreeView
1) Load page method is executed but it does nothing.
2) MyLVPaging_TreeView_SelectedNodeChanged is executed. Here I can see that everything has the correct value:
protected void MyLVPaging_TreeView_SelectedNodeChanged(object sender, EventArgs e) {
this.ViewState[“PageIndex”] =
int.Parse(this.MyLVPaging_TreeView.SelectedNode.Value); // <– The new page
((DAO)this.ViewState[“DAO”]).PageIndex =
(int)this.ViewState[“PageIndex”]; // <– Index saved
// After setting the new index, GetData will return another set of results
this.MyLV_ListView.DataSource =
((DAO)this.ViewState[“DAO”]).GetData(); // <– DataSource is changed
}
3) ListView‘s ItemDataBound event handler is executed. Well, here, debugging I can see the same as before, I mean, I can see that new values, new Data have been bound to the ListView, in fact I can inspect new values that are assigned to the template item.
Obviously, as before, after the method is executed, the debugger cursor moves to MyWebControl.ascx.cs to the control’s Page_Load method.
HERE IS EVIL: I can see the following:
protected void Page_Load(object sender, EventArgs e) {
// Note: inspecting this.IsPostBack I get true!
this.MyLabel1.Text = this.str1; // <-- this.str1 is null and also this.MyLabel1.Text is null.
this.MyLabel2.Text = this.str2; // <-- this.str2 is null and also this.MyLabel1.Text is null. view itemdatabound event handler.
}
Well!!! IT LOSES STATE AND ALSO VALUES
PASSED BY THE LIST VIEW
HANDLER!!!!!!!!!
My question
What the hell is going on???
Notes
1) Please, do not focus too much on how I managed data binding on Load and PreRender events… I have a feeling this is not the error! I guess it is something related to the page lifecycle. However, if you think that this is important detail, let me know.
2) DAO and its function GetData() are a way to let you understand as quick as possible the scenario which is a little bit more articulated, but with the same exact structure I’ve shown here.
Thank you for your help.
From my comments:
Andry:
Me: