Thursday, February 7, 2008

OutOfRangeAwareDropDownList - .NET 2.0, C#

Another big problem I've come across (and I know I'm not the only one) has to do with using databinding on DropDownLists.

There is a common issue that crops up. Say the items in the listbox get out of sync with records in the backend datastore. For example the value "foo" is in a data record, but "foo" doesn't exist in the DropDownList. When you pull up the record using databinding (SelectedValue='<%# Bind("FooColumn") %>') you'll get an error like "'lstBadFooList' has a SelectedValue which is invalid because it does not exist in the list of items. Parameter name: value".

I've come up with a solution for this! It's a custom control that I've named, appropriately enough, OutOfRangeAwareDropDownList. What it does is check to see if it's throwing this error, and if it is, it just decides to bind to nothing. Not much to it, but it saves a lot of headaches.

Here's the code:

using System;
using System.Collections;
using System.Web.UI.WebControls;

//this dropdown control overrides the databinding event
//it is used to overcome an issue where an element originally put into the record
//no longer exists in the dropdown list

namespace kevnls.controls
{
public class OutOfRangeAwareDropDownList : DropDownList
{
protected override void PerformDataBinding(IEnumerable dataSource)
{
try
{
base.PerformDataBinding(dataSource);
}
catch (ArgumentOutOfRangeException)
{
base.SelectedIndex = -1;
}
}
}
}

All you have to do is compile this into a .dll and then use it in your project. The absolute easiest way to use it in Visual Studio 2005 is to right-click in your toolbox and select "Choose Items..." and find the .dll, and a new control will show up in your toolbox. Then you can just drag it into your .aspx page like any other control and Visual Studio will take care of registering the assembly and importing the reference. Simple as that.

Get unbound DropDownList value in GridView - .NET 2.0, C#

I've been looking for a solution to a problem when using one dropdownlist within a gridview to populate another dropdownlist.

The issue is that the databinding is broken on the second dropdownlist once you do this, so you can't use SelectedValue='<%# Bind("....") %>' or else you'll get an error when you go to do an update to your database. ("Databinding methods such as Eval(), XPath(), and Bind() can only be used in the context of a databound control. ").

The solution I've come up with is to create a hidden field that can hold the value of the selection from the second dropdownlist, then use a client-side javascript to find and store the value from this dropdownlist into the hidden field. The control is server-side so when I do my database update I can simply find the hiddenfield.value.

I've attached the relevant code below. The only trick is to do a little server-side/client-side tie by passing the javascript the ClientIDs of the controls that are involved (since they'll be named dynamically).

Here's the relevant code snippets:

***javascript***

<script type="text/javascript">
function jobTitleToHiddenField(strListClientID, strHiddenFieldClientID)
{
var strJobTitle = document.getElementById(strListClientID).value;
var hiddenField = document.getElementById(strHiddenFieldClientID);
hiddenField.value = strJobTitle;
}
</script>

***hidden textbox***

<asp:HiddenField ID="hfJobTitle" runat="server" />

***dropdown in template field***

<EditItemTemplate>
<asp:DropDownList ID="lstJobTitles" runat="server" DataSourceID="dsJobTitles"
DataTextField="Title" DataValueField="Title" OnPreRender="lstJobTitles_PreRender" ></asp:DropDownList>
</EditItemTemplate>

***code behind***

protected void lstJobTitles_PreRender(object sender, EventArgs e)
{
DropDownList ddlist = sender as DropDownList;
string strListClientID = ddlist.ClientID;
string strHiddenFieldClientID = hfJobTitle.ClientID;
ddlist.Attributes.Add("onchange", "jobTitleToHiddenField(’" + strListClientID + "’, ‘" + strHiddenFieldClientID + "’);");
}

then you can get the value in the code behind anywhere you need it with…

string strTitle = hfJobTitle.Value;