A DropDownList Bug

I found there was a bug in System.Web.UI.WebControls.DropDownList recently. When I created items for a DropDownList control using DropDownList.DataBind method, an exception was always thrown. The error message looked like:

'DropDownList_Option' has a SelectedValue which is invalid because it does not exist in the list of items. 
Parameter name: value

At first, I thought maybe I should clear the selection before invoking DataBind method because the word "SelectedValue" was mentioned in the error. I then began to try the following methods:

  • DropDownList_Option.ClearSelection();
  • DropDownList_Option.SelectedIndex = -1;

But unfortunatelly none of the them worked. The exception was still thrown. I got confused at that time. Because abviously we had nothing selected. With "Reflector"'s help, I finally found the reason. It seemed it was a bug there which I would introduce the details below. To bypass the bug, you can just simply try:


  
  
1
DropDownList_Option.SelectedValue = null;

It was so strange, wasn't it?

How to reproduce the bug

It's very simple to reproduce the bug. Just use the code segment below:

    
  
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// MyDataItem class definition // public class MyDataItem
                {
                private string _text;
                private string _value;
                public MyDataItem(string text, string value)
                {
                _text = text;
                _value = value;
                }
                public string Text
                {
                get { return _text; }
                }
                public string Value
                {
                get { return _value; }
                }
                }
                // Reproduce the DropDownList bug here // e.g. we have a DropDownList variable called DropDownList_Option for now. // List
       
        items1 = new List
       
       ();
                items1.Add(new MyDataItem("Text1", "Value1"));
                items1.Add(new MyDataItem("Text2", "Value2"));
                items1.Add(new MyDataItem("Text3", "Value3"));
                DropDownList_Option.DataTextField = "Text";
                DropDownList_Option.DataValueField = "Value";
                DropDownList_Option.DataSource = items1;
                DropDownList_Option.DataBind();
                // Makes the first item selected. // Please note if we use "DropDownList_Option.SelectedIndex = 0;" here, there will be no exceptions. // DropDownList_Option.SelectedValue = "Value1";
                List
       
        items2 = new List
       
       ();
                items2.Add(new MyDataItem("2 - Text1", "2 - Value1"));
                items2.Add(new MyDataItem("2 - Text2", "2 - Value2"));
                items2.Add(new MyDataItem("2 - Text3", "2 - Value3"));
                DropDownList_Option.SelectedIndex = -1;
                DropDownList_Option.ClearSelection();
                DropDownList_Option.DataSource = items2;
                DropDownList_Option.DataBind();  // An exception will be thrown here!

Why does the bug appear?

To realise why the bug always appears, we have to have a deep look into .Net Framework source code. We are so lucky that we have Reflector. It provides us the valuable source code. Thanks Reflector! :)

  • The exception is thrown in ListControl.PerformDataBinding method:
                
        
        
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // Generated code by Reflector if(dataSource != null)
                            {
                            // Creates ListItem(s) here   // .... }
                            if (this.cachedSelectedValue != null)
                            {
                            int num = -1;
                            num = this.Items.FindByValueInternal(this.cachedSelectedValue, true);
                            if (-1 == num)
                            {
                            throw new ArgumentOutOfRangeException("value", SR.GetString("ListControl_SelectionOutOfRange", new object[] { this.ID, "SelectedValue" }));
                            }
                            // .... }
  • I've simplified the code segment to let us read it a bit easier than before. We can see that a new variable called "cachedSelectedValue" is introduced here. If its value is not null, then an exception will probably thown. So we'll concentrate on the following questions for now:
    • Where is "cachedSelectedValue" assigned?
    • Will "cachedSelectedValue" be changed if we invoke "ListControl.ClearSelection", or set values for "ListControl.SelectedValue" and "ListControl.SelectedIndex"?
  • Let's move on. "cachedSelectedValue" is only assigned by "ListControl.SelectedValue"(except ListControl.PerformDataBinding)! Neither "ListControl.ClearSelection" nor "ListControl.SelectedIndex" will change "cachedSelectedValue".
  • So the problem comes. When we're assigning a value to the property "SelectedValue", "cachedSelectedValue" is also assigned with the same value. Then we want to clear all the selections. We try to invoke "ClearSelection" method, or assign -1 to the property "SelectedIndex". They all make sense. However, they don't clear "cachedSelectedValue" variable at the same time. They make the DropDownList instance go into an inconsistent state. Finally when calling DropDownList.DataBind method, we get an exception thrown.

There are also 2 similar variables called "cachedSelectedIndex/cachedSelectedIndices" in ListControl. I'm not sure whether they'll cause exceptions or not. You can try it if you're interested in it.

你可能感兴趣的:(list)