ListControl SelectedValue inconsistencies
I really dislike the ListControl SelectedValue implementation – this damn property is causing me all sorts of grief on occasion with its inconsistencies.
Here’s a scenario I’ve just run into again: I have a form that binds values early in the form cycle. It’s mostly an AJAX driven form so there are only a few isolated POSTbacks that occur that repost the entire page because they update most of the content anyway.
So the list is loaded up in OnInit() and the initial SelectedValue is set as part of the startup code. POSTBACKS then set the value to the last user set value and all that works well enough. Here’s the basic code that deals with the initial and POSTback assignments:
private void GetResourceSet()
{
this.ResourceSet = Request.Form[this.lstResourceSet.UniqueID];
if (ResourceSet == null)
this.ResourceSet = Request.QueryString["ResourceSet"];
if (this.ResourceSet == null)
this.ResourceSet = ViewState["ResourceSet"] as string;
this.ResourceSet = this.ResourceSet.ToLower();
if (!string.IsNullOrEmpty(this.ResourceSet))
ViewState["ResourceSet"] = this.ResourceSet;
DataTable dt = Manager.GetAllResourceSets(ResourceListingTypes.AllResources);
this.lstResourceSet.DataSource = dt;
this.lstResourceSet.DataValueField = "ResourceSet";
this.lstResourceSet.DataBind();
if (this.ResourceSet != null)
this.lstResourceSet.SelectedValue = this.ResourceSet;
}
The problem comes in if I need to change the selected value as part of an event LATER in the page cycle. Basically I have an event that fires and allows renaming one of the items which in turn changes the entire list interface of several lists of the form so they need to be reloaded. Here is the key that reloads the ResourceSet list:
protected void btnRenameResourceSet_Click(object sender, EventArgs e)
{
if (!this.Manager.RenameResourceSet(this.txtOldResourceSet.Text,
this.txtRenamedResourceSet.Text))
this.ErrorDisplay.ShowError(this.Manager.ErrorMessage);
else
{
this.lstResourceSet.ClearSelection();
// this.lstResourceSet.SelectedValue = null;
// this.lstResourceSet.SelectedIndex = -1;
// *** Refresh and reset the resource list
// *** Reloads the list from the data base calls databind()
this.GetResourceSet();
this.ErrorDisplay.ShowMessage("ResourceSet renamed");
}
}
The problem is when I call GetResourceSet() again I get an error:
'lstResourceSet' has a SelectedValue which is invalid because it does not exist in the list of items.
Ok, fine but how the heck can I clear the selection in the first place so that ASP.NET DOESN’T complain about the existing value so it can safely DataBind?
I’ve tried ClearSelection() – doesn’t work. I tried setting the SelectedValue to null which doesn’t work at all (the old selection stays active) and setting the SelectedIndex also has no effect because the SelectedValue lookup is what’s really being checked.
This seems to work however:
this.lstResourceSet.SelectedValue = this.txtRenamedResourceSet.Text.ToLower();
// *** Refresh and reset the resource list
this.GetResourceSet();
although I don’t really understand why that is working. SelectedValue assignments are supposed to fail if the value doesn’t exist in the list of items yet and in this case the item DOESN’T exist yet, because the list hasn’t been rebound with the new value.
I took a look at the SelectedValue implementation in System.Web with Reflector, and looking at this implementation I’m really scratching my head. It looks like there really is no way to clear out a selection of the SelectedValue:
set
{
if (this.Items.Count != 0)
{
if ((value == null) || (base.DesignMode && (value.Length == 0)))
{
this.ClearSelection();
return;
}
ListItem item1 = this.Items.FindByValue(value);
if ((((this.Page != null) && this.Page.IsPostBack) && this._stateLoaded) && (item1 == null))
{
throw new ArgumentOutOfRangeException("value", SR.GetString("ListControl_SelectionOutOfRange", new object[] { this.ID, "SelectedValue" }));
}
if (item1 != null)
{
this.ClearSelection();
item1.Selected = true;
}
}
this.cachedSelectedValue = value;
}
Notice the call to:
ListItem item1 = this.Items.FindByValue(value);
Which in theory should fail because the txtRenamedResourceSet.Text value doesn’t exist in the list yet.
Even odder is hooking the debugger up to this code:
this.lstResourceSet.SelectedValue = this.txtRenamedResourceSet.Text.ToLower();
and looking at lstREsourceSet.SelectedValue after the assignment – it’s showing the old value rather than the updated one, YET the DataBind() operation actually works with this same existing SelectedValue as when I hadn’t assigned this value.
I suppose this code would be safer:
// *** Force the selected value to be set
this.lstResourceSet.Items.Add(new ListItem("", this.txtRenamedResourceSet.Text.ToLower()));
this.lstResourceSet.SelectedValue = this.txtRenamedResourceSet.Text.ToLower();
Either way it seems it’s all rather convoluted to do something as simple as resetting a list selection value. I have this sinking feeling that I must be overlooking something simple that’s escaping me…
Other Posts you might also like
The Voices of Reason
# re: ListControl SelectedValue inconsistencies
IMHO, the inter-dependancy between SelectedValue and SelectedIndex causes some very subtle postbcak problems.
hope this helps
Abhinav
# re: ListControl SelectedValue inconsistencies
You can see it in ListControl.PerformDataBinding
that's it:
if (this.cachedSelectedValue != null)
{
int num1 = -1;
num1 = this.Items.FindByValueInternal(this.cachedSelectedValue, true);
if (-1 == num1)
{
throw new ArgumentOutOfRangeException("value", SR.GetString("ListControl_SelectionOutOfRange", new object[] { this.ID, "SelectedValue" }));
}
if ((this.cachedSelectedIndex != -1) && (this.cachedSelectedIndex != num1))
{
throw new ArgumentException(SR.GetString("Attributes_mutually_exclusive", new object[] { "SelectedIndex", "SelectedValue" }));
}
this.SelectedIndex = num1;
this.cachedSelectedValue = null;
this.cachedSelectedIndex = -1;
}
else if (this.cachedSelectedIndex != -1)
{
this.SelectedIndex = this.cachedSelectedIndex;
this.cachedSelectedIndex = -1;
}
# re: ListControl SelectedValue inconsistencies
In 1.1 I override the DataBind() method with this:
public override void DataBind()
{
base.ClearSelection();
base.DataBind();
this.AttemptSetBaseSelectedValue();
}
Which works fine in 1.1, no error. When I moved to ASP.Net 2.0 now I get the error you describe above!
UGH!!! Talk about a step backward.
The DropDownList implemenation is horrible from the ASP.Net 1.0 days. I don't understand why it is better to send a list down 2 times to a client once as HTML/text and the other time in the list. That's why we changed the DropDownList to never save the list in the viewstate (only the selected value) and we re-bind the list from the DB/cache or whatever. It worked great until ASP.Net 2.0.
I'll let you know if I come up with a solution short of catching the error in my DataBind() method, which I don't want to do!
# re: ListControl SelectedValue inconsistencies
To this day even knowing all of this I run into situations where things just don't work right and it takes all sorts of futzing to get it tweaked to work. In some situations using ViewState/ControlState is the only way.
The list control model is horribly designed because it clearly assumes it can use ViewState and once that's not there it gets nasty...
# re: ListControl SelectedValue inconsistencies
public override void DataBind()
{
base.ClearSelection();
this.Items.Clear();
base.SelectedValue = null;
base.DataBind();
this.AttemptSetBaseSelectedValue();
}
# re: ListControl SelectedValue inconsistencies
I just saw your last post about rebinding. We always bind in either page load or earlier, but this error occurred because of our AJAX libraries doing a delayed load on a drop down list that initialized with one item in the list with a value of "". When the user focusses on the drop down list we then invoke an AJAX call back to the server to obtain the list values from the server and output them. As far as the server is concerned the AJAX post back (which is what we call it) appears to be a normal post back. In the even handler we output the JS code to update the drop down list. Anyway the error would occur because the item with value of "" did not appear in the list at all, and now the new list to be databound doesn't have a value of "". What is weird is I call "ClearSelection" first then rebind and call a custom method we have that attempts to set the selected value if the item i still in the list.
This all worked fine in 1.1, in 2.0, I have to manually clear the items (which are empty anyway) then manually set "SelectedValue" to null, then rebind and it works like it did for us in the 1.1 days.
This is what it should do under all circumstances. I don't think a user should get an error when they rebind the list just because the old selected value is there. I would think that it should just clear the value. That's the behavior I would expect. Anyway thank god we can subclass things and take them as our own and fix problems like this. I just wanted to avoid a try catch() at all cost. I think that is a horrible way to code if you have to do that.
Thanks for the blog, you're blogs rock! You helped us determine when to move to ASP.Net 2.0 based on waiting for the Web Application project which seems to be pretty good for the most part.
Found one thing that sucks in the new 2.0. If we upgrade an old form, and we have a base page that has a property for the header control or footer control, once upgraded to the new design, each time edit the front page, it attempts to put the control reference in the design page, even though the control is already declared in the base. It issues a warning for the new item without new, but then our base code croaks because the control is null in the base, being redclared. It just does that automatically. Anyway that is for another blog.
# re: ListControl SelectedValue inconsistencies
The issue I'm talking about goes like this: Say you create your control to auto-assign the selected value in LoadControlState() (which occurs after OnInit() but before OnLoad()) so now you are setting a SelectedValue to a list that may not yet have been databound. So either you have to check for the contents of the list and not bind or you end up failing on the selectedvalue assign. If you do bind in Load() you have to pretty much assign the selected value after that in order for SelectedValue to make any sense. You see the dilemma here <s>...
Hmmm... another option might be to 'carry' the selected value in a variable and then assign it in OnPreRender() after events have fired. So any assignment to SelectedValue sets a temporary state variable and is only assigned explicitly in OnPreRender. That might work in most scenarios. Of course that may yet have other side effects with MS Ajax...
# re: ListControl SelectedValue inconsistencies
I think that the trouble comes when calling databind on a dropdown when its current selected value does not fit into the list. In my case, I am trying to populate during the OnInit phase of the dropdown (inside a datagrid template column) and the selected value won't fit because the postback calls have not happened yet. In your case, you are re-populating but the selected value does not fit because you have just renamed. In both cases, we call databind() when the selected value (which we plan to throw away) does not fit the datasource.
On a more general note, I have found that by populating during OnInit of the dropdownlist, leaving viewstate enabled, and manually binding a datatable to the grid, I seem to be getting what I need. Smaller viewstate which only has selected value and not list contents and the ability to detect onchange events.
# re: ListControl SelectedValue inconsistencies
The main issue is that the actual way that the value gets assigned is horribly complex and quite inconsistent based on a myriad of different settings. And leaving ViewState on for large lists is simply not an option in many situations. Even using Controlstate with custom classes can have unexpected issues. <shrug> I find this one of the more frustrating things in the ASP.NET control architecture - one of those things that I run into over and over and waste often an hour a pop on because it seems the situation is never quite the same so no easy patterns (that I can discern) apply.
# re: ListControl SelectedValue inconsistencies
Control.DataSource = Nothing
Control.DataBind()
Now try to DataBind to a new DataSource. There seems to be something funky going on under the hood that causes the SelectedValue property to retain the old value from the previous DataBind call. I hope it helps, it recently solved a similar problem with a DropDownList for me (I posted a more detailed solution to that here too).
# re: ListControl SelectedValue inconsistencies
ddlTarget.Items.Clear()
ddlTarget.SelectedValue = Nothing
This seems to successfully clear the selected value so that .NET won't try to re-apply it after it databinds with the new dataset.
You can, of course, copy the selected value to some variable first if you need to preserve it for other reasons.
# re: ListControl SelectedValue inconsistencies
Our business logic takes care of validating our database operation and if the foreign-key (as a drop-list usually relates to one) does not exist, we have an error.
The only time I need to repopulate the drop-list is later on prior to rendering - that's when I know the data is going to be re-displayed to the user, and that seems the ideal time to populate the list.
# re: ListControl SelectedValue inconsistencies
If you have to figure out exactly when to databind() and when not to it can get kinda messy quickly. You still have validation error scenario and in most apps the ability to re-edit the data even after a successful save operation.
# re: ListControl SelectedValue inconsistencies
All this is currently just speculation, because I'm yet to see how it works in practice, but ...
OnPreRender( ) gets around the messyness nicely because the DropDownList( ) implementation calls EnsureDataBound, meaning you can bind the data very late. The trick, as noted earlier in these posts, is to make sure your Items container is empty and then set the SelectedValue - the ListControl caches the value so a future-occurring DataBind( ) will utilise the cached value.
So rather than re-populating a drop-list just for the sake of a PostBack operation, you can delay it a little so that if the PostBack is successful (ie. the form is saved, and you are going elsewhere), then you haven't wasted effort re-loading that data.
Either way, that delayed population of the drop-down-list will pay off either as less load on the database server (if you go straight to the database server for the data - as we do), or less memory consumed on the web-server (if you cache the table in a Session object), or less bandwidth consumed (by not having to store the items in ViewState - which I consider only workable on a LAN and even then only for small-moderately sized lists).
PS: Great blog, by the way.
# re: ListControl SelectedValue inconsistencies
ddlist.Items.Clear();
ddlist.SelectedValue = null;
# re: ListControl SelectedValue inconsistencies
SelectedValue="<%#Eval("acolumn")%>"
First, I bind the item with a valid value, the first one.
Protected Sub chkBoxList_DataBinding(ByVal sender As System.Object, ByVal e As System.EventArgs)
Dim box As CheckBoxList = CType(sender, CheckBoxList)
chkbox.SelectedValue = chkbox.Items(0).Value
End SubThen, cause I don't want this value selected, I uncheck it once the item is bound
Protected Sub chkBoxList_DataBound(ByVal sender As System.Object, ByVal e As System.EventArgs)
Dim chkbox As CheckBoxList = CType(sender, CheckBoxList)
chkbox.SelectedIndex = -1
chkbox.SelectedValue = Nothing
End SubHope it helps
# re: ListControl SelectedValue inconsistencies
It is quite interresting to see "Inside the box" that the exception of SelectedValue only is thrown on postback calls.
I noticed this when I had my Selected value setting within try/catch. With postback calls this worked perfectly (or at least as I axpected). But with non-postback calls, the setting with illegal values just passed without notice, until it crashed later on when it was outside my control.
Any idea why that is the case?
Why is the exception suspended because of a non-postback call?
# Dropdownlist Selected value
# re: ListControl SelectedValue inconsistencies
my_DDL.Items.Clear();
my_DDL.SelectedValue = null;
# re: ListControl SelectedValue inconsistencies
I've found it much better to use SelectedIndex throughout if you possibly can. So long as you never assigned a SelectedValue programatically, it'll never look for one.
# re: ListControl SelectedValue inconsistencies
We went ahead and wrote a ListHeper static class with a bunch of overloads that added items one-by-one (as you did in your final code snippet). I have no idea if it's comparable performance wise, but really, we got that error so often that we gave up using data binding and rolled our own.
If I had any real motivation, I'd probably derive from the control and override the DataBind method to really encapsulate the one-by-one method.