I sat there and blinked at the ArgumentOutOfRangeException.  It was just a DropDownList that had the selected value data bound inside a FormView.  How can we prevent this from happening?  I did a little digging on how to handle this odd "spot" and I found this article on a subclass of the DropDownList control.  It is an interesting article, and I almost went this route but it has a draw back I did not like.  The articles solution is to add the missing value to the DropDownList in an over-ridden OnDataBinding method. 

It is a clean solution and it works.  It could be easily compiled into an assembly and if you were feeling really ambitious it could modified to fire a "ValueNotFoundEvent" where you could change the value or bubble up an exception that includes the value that the drop down list does not have; with those features added and removing the reference to DataRowView so it could work with an ObjectDataSource I think it would be a useful control in everyone's toolbox.

Consider the following DropDownList and FormView

  <asp:FormView ID="FormView1" runat="server" DataSourceID="ObjectDataSource1">
          <ItemTemplate>
              <asp:DropDownList ID="ddlSomething" runat="server"
                   SelectedValue='<%# Bind("Value") %>'>
                  <asp:ListItem>-choose-</asp:ListItem>
                  <asp:ListItem>one</asp:ListItem>
                  <asp:ListItem>two</asp:ListItem>
                  <asp:ListItem>three</asp:ListItem>
                  <asp:ListItem>five</asp:ListItem>
              </asp:DropDownList>
          </ItemTemplate>
      </asp:FormView>
      <asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
           OldValuesParameterFormatString="original_{0}" SelectMethod="GetTest"
           TypeName="UserInterface.TestSource">
</asp:ObjectDataSource>

For reference, test and TestSource are as follows

  namespace UserInterface
  {
      [DataObject(true)]
      public class test
      {
          public test(String value)
          {
              _value = value;
          }

          private string _value;

          [DataObjectField(false,false,false)]
          public string Value
          {
              get { return _value; }
              set { _value = value; }
          }
      }
      [DataObject]
      public class TestSource
      {
          [DataObjectMethod(DataObjectMethodType.Select)]
          public test GetTest()
          {
              test result = new test("four");

              return result;
          }
        }  
}

Please make note, I'm not going into detail on DataSources, the DataObjectAttribute or how many of my own (or common best) practices I violated in the samples in this article, let's leave it at I feel like I need a shower having written it and move on.

If you're in a crunch and cant add a custom control to your solution there is a way to handle this with an event.

Alter the DropDownList to look this...

  <asp:DropDownList ID="ddlSomething" runat="server"
                   SelectedValue='<%# Bind("Value") %>'
                   OnDataBinding="DropDownList_DataBinding">

and add the following into the pages code behind...

   1:  protected void DropDownList_DataBinding(object sender, EventArgs e)
   2:          {
   3:              DropDownList theDropDownList = (DropDownList)sender;
   4:              theDropDownList.DataBinding -= new EventHandler(DropDownList_DataBinding);
   5:              try
   6:              {
   7:                  theDropDownList.DataBind();
   8:              }
   9:              catch(ArgumentOutOfRangeException)
  10:              {
  11:                  UserInterface.test item = (UserInterface.test)((IDataItemContainer)theDropDownList.NamingContainer).DataItem;
  12:                  //do whatever here, in this case we will just add the item
  13:                  theDropDownList.Items.Add(item.Value);
  14:              }
  15:          }

Now you will get the list and "four" will be added and selected.  Replace line 13 with whatever you would like to do for checks and tests.

ArgumentOutOfRangeException does have a property called "ActualValue" but the .net framework does not fill this this property when throwing the exception. 

The ActualValue property is not used within the .NET Framework class library. It carries a null value in all the ArgumentOutOfRangeException objects thrown by the .NET Framework class library. The ActualValue property is provided so that applications can use the available argument value.

Why does this work? The DataBinding event fires before the DataBind happens. 

  • On line 3 we type cast the sender as a DropDownList, because the event is wired into the DropDownList on the aspx page we know this the case (you could add a type check if you wanted). 
  • On line 4 we remove the event handler to avoid an infinite loop because calling DataBind on the DropDownList (line 7) will fire the DataBinding event again. 
  • With line 5 to 8 we try to DataBind the DropDownList.
  • Line 9 catches the only exception we are interested in, the ArgumentOutOfRangeException.  Any other exception should bubble up.  you could add a 2nd catch block under the ArgumentOutOfRangeException block like catch(Exception) and handle it there if you did not want it to bubble up.  Notice I do not use the ArgumentOutOfRangeException that was thrown anywhere and that I did not make a variable for it.  This avoids the warning "variable err is declared but never used" that you get when you use "catch(ArgumentOutOfRangeException err)".
  • Line 11 get the current UserInterface.test item that the FormView is attempting to bind.  By replacing UserInterface.test with the correct object you can reuse this snip of code. In theory one should be able to use generics with this but I have not tested or tried that.  If I was going to reuse this enough to try generics; I would just subclass the DropDownList, override the DataBinding method and fire a custom event when the value was not in the items collection instead of tinkering with Generics.  If ActualValue actually had the value in it, this this would be unneeded, declaring the variable on line 9 would allow us to use err.ActualValue.
  • Line 13 adds the value to the DropDownList.

This hack is quick and clean.  It leaves a very small footprint in the code; but you can not change the value, it does not work.  Really, a sub classed DropDownList that fires off an event that is able to return back the new value to use is a better solution to the problem.

How does that look? I declared the class within the UserInterface namespace

      public class DataBindDropDownList : System.Web.UI.WebControls.DropDownList
      {
          public class ValueNotFoundArgs : EventArgs
          {
              public ValueNotFoundArgs(string value)
                  : this(value, false)
              {
              }

              public ValueNotFoundArgs(string value, Boolean addNewValue)
                  : base()
              {
                  _addValue = addNewValue;
                  _value = value;
                  _displayName = value;
              }

              private string _value;

              public string Value
              {
                  get { return _value; }
                  set { _value = value; }
              }

              private Boolean _addValue;

              public Boolean AddValueIfItDoesNotExist
              {
                  get { return _addValue; }
                  set { _addValue = value; }
              }

              private string _displayName;

              public string DisplayName
              {
                  get { return _displayName; }
                  set { _displayName = value; }
              }
          }
            public delegate void ValueNotFoundEventHandler(object sender, ValueNotFoundArgs e);

          public event ValueNotFoundEventHandler ValueNotFound;
          private string _cachedValue = "";
          public override string SelectedValue
          {
              get
              {
                  return base.SelectedValue;
              }
              set
              {
                  base.SelectedValue = value;
                  _cachedValue = value;
              }
          }

          private void ThrowArgumentOutOfRangeException(string paramName, string value)
          {
              throw new ArgumentOutOfRangeException(paramName, value, null);
          }

          protected override void OnDataBinding(EventArgs e)
          {

              try
              {
                  base.OnDataBinding(e);
              }
              catch (ArgumentOutOfRangeException err)
              {
                  if (ValueNotFound != null)
                  {
                      ValueNotFoundArgs notFoundArgs = new ValueNotFoundArgs(_cachedValue, false);
                      ValueNotFound(this, notFoundArgs);

                      this.ClearSelection();

                      System.Web.UI.WebControls.ListItem item = this.Items.FindByValue(notFoundArgs.Value);
                      if (item != null)
                          item.Selected = true;
                      else if (notFoundArgs.AddValueIfItDoesNotExist)
                      {
                          item =
                             new System.Web.UI.WebControls.ListItem(notFoundArgs.DisplayName, notFoundArgs.Value);
                          item.Selected = true;
                          this.Items.Add(item);
                      }
                      else
                      {
                          ThrowArgumentOutOfRangeException(err.ParamName, notFoundArgs.Value);
                      }
                  }
                  else
                  {
                      ThrowArgumentOutOfRangeException(err.ParamName, _cachedValue);
                  }
              }
          }
      }

and the aspx becomes...

<cc1:DataBindDropDownList ID="ddlSomething" runat="server" OnValueNotFound="DataBindDropDownList_ValueNotFound"
         SelectedValue='<%# Bind("Value") %>' >
        <asp:ListItem>-choose-</asp:ListItem>
        <asp:ListItem>one</asp:ListItem>
        <asp:ListItem>two</asp:ListItem>
        <asp:ListItem>three</asp:ListItem>
        <asp:ListItem>five</asp:ListItem>
  </cc1:DataBindDropDownList>

and in the pages code behind...

         protected void DataBindDropDownList_ValueNotFound(object sender,           UserInterface.DataBindDropDownList.ValueNotFoundArgs e) 
         {
              e.AddValueIfItDoesNotExist = false;
              e.Value = "6";
          }

The aspx and page code behind do not look a whole lot different.  Depending on your assemblies/app_code directory the tags in your aspx may look different.   I can still get the same ArgumentOutOfRangeException as before; expect now I get the ActualValue property populated when the exception is thrown. 

Change the page code behind slightly...

  protected void DataBindDropDownList_ValueNotFound(object sender,
          UserInterface.DataBindDropDownList.ValueNotFoundArgs e)
          {
              e.AddValueIfItDoesNotExist = true;
              e.Value = "6";
          }

and the missing value is added to the DropDownList.

More importantly the ValueNotFound event allows a logic layer to be hooked in to change the value or add the value to the list.  There is no need to worry about DataSets, ObjectDataSources, DataRows, DataRowViews or type casting. 

How does it work?

To understand what is going on taking a look at the ListControl with Reflector helps a great deal.  Basically, ListControl's set accessor for SelectedValue checks to see if the items collection has been populated yet and puts the new selected value into a cache until the controls DataBind method is invoked.  Once the items list is built the cached value is pulled out and made active or throws an exception if it is not in the items collection.  The DataBindDropDownList  does the same thing, it caches the selected value and if the base class (DropDownList) raises the ArgumentOutOfRangeException it fires the ValueNotFoundEvent.  If the event is unused a ArgumentOutOfRangeException with the ActualValue populated is thrown.  If the event is used the value in the events ValueNotFoundArgs is evaluated.  If it finds the value, which the event may have changed, in the DataBindDropDownList items it is set to be the selected item.  If the value is not found it checks to see if AddValueIfItDoesNotExist is true.  If it is we create the ListItem with the value and the specified DisplayName.  If AddValueIfItDoesNotExist is false a ArgumentOutOfRangeException is thrown with the value from the ValueNotFoundArgs as the ActualValue because that was the last value we tried to set.

Between the 3 different ways outlined here, the original method from attractor's article, the quick event hack and the event based sub-class of the DropDownList control you should be able to address the problem of the missing value in a data bound DropDownList in a way that fits your code/environment.

Back when I used Visual Studio 2005 I would use the quick event hack because of the "issues" getting VS2005 to see controls in an assembly and display them in the toolbox.  Because 2008 has addressed the "common issues" with the toolbox and assemblies I would rather build the DataBindDropDownList in an assembly, add the reference to the project and use it.

Happy coding.