Aarrgh. Am I the only one who's repeatedly cursing the way ListBoxes and the OnSelectedIndexChanged event and SelectedValue assignments work in ASP.NET when ViewState is off?
If you turn off ViewState on a ListBox or DropDownList control it's a real bitch to deal with the OnSelectedIndexChanged event. Take the following scenario assuming you set ViewState off for the lstCustomers control:
protected void Page_Load(object sender, EventArgs e)
{
Customer = new busCustomer();
Customer.GetCustomerList();
this.lstCustomers.DataSource = Customer.DataSet.Tables["TCustomerList"];
this.lstCustomers.DataValueField = "CustomerId";
this.lstCustomers.DataTextField = "CompanyName";
this.lstCustomers.DataBind();
}
If you've tried this you've probably know that this doesn't work quite the way as is expected if you have AutoPostBack and the OnSelectedIndexChanged hooked up to the listbox.
This code works to display the customer information each time you load the page, but there are a couple of problems with this:
First if you do this in Page_Load(), you end up with no selected value because by the time Page_Load fires the selected value has already been updated from the POST buffer. This is easy to fix with:
if (this.IsPostBack)
this.lstCustomers.SelectedValue = Request.Form[lstCustomers.UniqueID];
But this isn't exactly a good idea. Because of a more serious issue. If you bind in Page_Load() the SelectedIndexChanged event doesn't get fired correctly because the value wasn't set correctly when the POST data was assigned to the control – since the list wasn't loaded yet SelectedValue couldn't be assigned. Manually assigning the value in Page_Load doesn't change this behavior.
There's is a simple but not so obvious solution and that's to bind in OnInit() of the Page:
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
Customer = new busCustomer();
Customer.GetCustomerList();
this.lstCustomers.DataSource = Customer.DataSet.Tables["TCustomerList"];
this.lstCustomers.DataValueField = "CustomerId";
this.lstCustomers.DataTextField = "CompanyName";
this.lstCustomers.DataBind();
}
Now it works as you would expect. Sort of… most of the time. This still won't work correctly if you fire other events on the same page (like a Button Click). If you do you'll find that the SelectedIndexChanged event fires as well…
Soooooo… this sucks. It seems to me in ASP.NET 2.0 this particular issue could have been easily fixed by putting the SelectedValue(s) into ControlState so it always gets stored. Can anybody think of why this wasn't done?
The only way I can figure out to get this to work using only page code is to manage my own ViewState for the control. I can add these two methods to the form:
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
this.ViewState["lstCustomers_SelectedValue"] = this.lstCustomers.SelectedValue;
}
protected override void LoadViewState(object savedState)
{
base.LoadViewState(savedState);
string Val = this.ViewState["lstCustomers_SelectedValue"] as string;
if (Val != null)
this.lstCustomers.SelectedValue = Val;
}
Now it works, but this is messy as hell. And it won't work if ViewState is off completely on the page. I'm not sure how that could be handled if ViewState is of, short of implementating a custom listbox control that implements controlstate for the SelectedValue property.
One workaround is my PreservePropertyControl I posted a while back. This control can track property values automatically. It stores and hooks ControlState events, so everything happens at the right time automatically. To use the control you add the control to the page like this:
<ww:PreservePropertyControl runat="server" ID="preserve">
<PreservedProperties>
<ww:PreservedProperty ID="PreservedProperty1" runat="server"
ControlId="lstCustomers"
Property="SelectedValue">
</ww:PreservedProperty>
</PreservedProperties>
</ww:PreservePropertyControl>
and it all works. You still need to remember to stick the databinding code into the OnInit() of the page so that the list is filled with data prior to ViewState/ControlState unbinding but otherwise nothing else needs to happen.
Now if you were using ViewState none of this would have to happen. ViewSate stores both the data of the ListBox and the SelectedValues. That's one of the main reasons not to use ViewState on a list unless it just has a few values in it. Otherwise you keep posting the content of the listbox back to the server and back on every postback.
Trouble in AJAX Land
All of this becomes a fairly major issue when you're dealing with AJAX implementations that rely on server side controls. Both Anthem and ATLAS (for server controls anyway) post back form data including viewstate and fire server side events like regular POSTBacks for Callbacks. Most of Anthems controls shuttle requests back to the server and capture the control's HTML content and shuttle that back to the client. ATLAS does a similar thing with UpdatePanel and the handful of Server controls like Timer that fire server side events.
ViewState is even more of an issue in Callback scenarios since ViewState may be going a heck of a lot more in callbacks, so having a large listbox stored in ViewState is going to defeat the whole light weight aspect of callback.