Extending the DropDownList to Support Enums
Date Published: 04 December 2007
Introduction
A fairly common task in some of the applications I work with is to provide a user with a DropDownList of options that is populated by an enum. This can easily be done with a little bit of code to bind a stock DropDownList control to the enum, but this kind of thing quickly starts to violate the DRY (Don't Repeat Yourself) principle, so I created a simple derived DropDownList control that takes advantage of Generics to make it especially effective and require less code and casting of data types.
Binding a Standard DropDownList to an Enum
Before we delve into the derived DropDownList, consider the code that it is meant to replace. Let's say that we have an enum that describes Status, and has possible values of Active, Inactive, Pending, and Declined. We'll say these are for Proposal, so we'll call the enum ProposalStatus.
Listing 1: ProposalStatus enum
public enum ProposalStatus
{
Active =1,
Inactive = 2,
Pending = 3,
Declined = 4
}
Now if we want to render a DropDownList that uses this enum as its data source, we would use code like the following:
Listing 2: Binding Names to DropDownList
if (!Page.IsPostBack)
{
ProposalStatusDropDownList.DataSource =
Enum.GetNames(typeof(ProposalStatus));
ProposalStatusDropDownList.DataBind();
}
This will render a DropDownList with display and value both as the name of the enum – the value is not stored in the DropDownList. To get at the value, you would use code like this:
Listing 3: Getting a Strongly Typed Enum Value from the DropDownList
protected void Button1_Click(object sender, EventArgs e)
{
ProposalStatus myStatus = (ProposalStatus)Enum.Parse(
typeof(ProposalStatus), ProposalStatusDropDownList.SelectedValue);
Label1.Text = myStatus.ToString();
}
Of course, if you actually want to store the enum’s numeric value as the value of the DropDownList, the most effective way to achieve that is to load up a SortedList<int,string>
with the values and their corresponding names, and then DataBind to this collection. This is easily done with a helper method like the one shown in Listing 4. Note that since we cannot use where T:System.Enum
the best we can do is constrain T to be a struct and then do a type check in code to verify it inherits from Enum.
Listing 4: Convert ENum to SortedList<string,int> Collection
public static SortedList<string, int> GetEnumDataSource<T>() where T:struct
{
Type myEnumType = typeof(T);
if (myEnumType.BaseType != typeof(Enum))
{
throw new ArgumentException("Type T must inherit from System.Enum.");
}
SortedList<string, int> returnCollection = new SortedList<string, int>();
string[] enumNames = Enum.GetNames(myEnumType);
for (int i = 0; i < enumNames.Length; i++)
{
returnCollection.Add(enumNames[i],
(int)Enum.Parse(myEnumType, enumNames[i]));
}
return returnCollection;
}
Then to use this method with a DropDownList we would use code like what is shown in Listing 5.
Listing 5: Bind to SortedList from Enum
ProposalStatusDropDownList2.DataSource =
GetEnumDataSource<ProposalStatus>();
ProposalStatusDropDownList2.DataValueField = "Value";
ProposalStatusDropDownList2.DataTextField = "Key";
ProposalStatusDropDownList2.DataBind();
Creating a Generic Enum DropDownList
Now that we’ve written a bunch of code to show how anybody can do this to a stock DropDownList control, let’s roll all of this code into a single reusable control, the EnumDropDownList
. One additional requirement we’ll add to this control is that it optionally include a ListItem
that simply displays “All” and has no associated enum value. We’ll use this when the DropDownList
is being used to display filters on search results or reports, and in this case the “All” value will mean that we don’t want to filter by this enum. Similarly, to make our filtering easier, we’ll also include a nullable integer property, SelectedIntegerValue
, since the data access layer is going to expect integer values for the statuses we choose to filter on (or null if none). The control will include the GetEnumDataSource<T>
method shown earlier, and of course this could be refactored out into a separate utility class where it would make more sense. I’ve included it in EnumDropDownList
to make the sample easier to follow. The complete source for EnumDropDownList
is shown in Listing 6.
Listing 6: EnumDropDownSource
public class EnumDropDownList<T> : DropDownList where T : struct
{
private bool _showAllOption = true;
public bool ShowAllOption
{
get
{
return _showAllOption;
}
set
{
_showAllOption = value;
}
}
public new T? SelectedValue
{
get
{
if (String.IsNullOrEmpty(this.SelectedItem.Text))
{
return null;
}
return (T)Enum.Parse(typeof(T), this.SelectedItem.Text);
}
}
public int? SelectedIntegerValue
{
get
{
if (ShowAllOption && this.SelectedIndex == 0)
{
return null;
}
if (String.IsNullOrEmpty(this.SelectedItem.Text))
{
return null;
}
return (int)Enum.Parse(typeof(T), this.SelectedValue.ToString());
}
}
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
if (typeof(T).BaseType != typeof(Enum))
{
throw new ArgumentException("Type T must inherit from System.Enum.");
}
BindData();
}
protected void BindData()
{
this.DataSource = GetEnumDataSource<T>();
this.DataTextField = "Key";
this.DataValueField = "Value";
this.DataBind();
if (ShowAllOption)
{
this.Items.Insert(0, new ListItem("All", ""));
}
}
public static SortedList<string, int> GetEnumDataSource<T>() where T : new()
{
Type myEnumType = typeof(T);
if (myEnumType.BaseType != typeof(Enum))
{
throw new ArgumentException("Type T must inherit from System.Enum.");
}
SortedList<string, int> returnCollection = new SortedList<string, int>();
string[] enumNames = Enum.GetNames(myEnumType);
for (int i = 0; i < enumNames.Length; i++)
{
returnCollection.Add(enumNames[i],
(int)Enum.Parse(myEnumType, enumNames[i]));
}
return returnCollection;
}
}
Now to use this control, unfortunately you can’t simply add markup to the ASPX page. Some options were discussed by Mikhail at Microsoft but I can’t find that anything was ever implemented up through ASP.NET 3.5. Thus, you can only instantiate this control from your C# or VB code, and then add it to the page using a Placeholder control or similar technique. This is fairly minor, however, and the resulting code in the page’s codebehind is shown in Listing 7. PlaceHolder1 is defined on the page where we want the DropDownList to be shown.
Listing 7: Rendering the Control in a PlaceHolder
protected EnumDropDownList<ProposalStatus> ProposalStatusDropDownList1;
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
ProposalStatusDropDownList1 = new EnumDropDownList<ProposalStatus>();
this.PlaceHolder1.Controls.Add(ProposalStatusDropDownList1);
}
Downloads
You can download the source for this sample here.
Resources
How to Bind Enum to DropDownList
Use Enum as DataSource for DropDownList
Generic Enum To List Converter
Why We Need Where T:Enum Constraint
Conclusion
In this article, Steve demonstrated how to create a generic Enum DropDownList control with the help of a sample application.
Originally published on ASPAlliance.com
About Ardalis
Software Architect
Steve is an experienced software architect and trainer, focusing on code quality and Domain-Driven Design with .NET.