I've been wanting to try this concept out for awhile, and I finally had a reasonable opportunity.  The idea was to slap a System.ComponentModel.Description attribute onto each field of an Enum, and then bind a dropdown list directly to the Enum.

I got it working and the code is pretty concise.  Please excuse the fact that I'm working in VB for this one; it's my DotNetNuke project, and I was already working in VB to edit some modules.  The code would be even prettier in C# I imagine.

First, let's decorate an enum. Import System.ComponentModel here.

   1: Public Enum RegionCode
   2:     <Description("-- Select --")> NotSelected = 0
   3:     <Description("US - Midwest")> Midwest = 1
   4:     <Description("US - Northeast")> Northeast = 2
   5:     <Description("US - Southeast")> Southeast = 3
   6:     <Description("US - Southwest")> Southwest = 4
   7:     <Description("US - West")> West = 5
   8:     Canada = 6
   9: End Enum

Next, we need to build a helper routine that can get the description off of a decorated enum field.  It must gracefully handle the case when a description is not specified, as with Canada.

In .NET 3.5, you'd probably want to build these as extension methods, which would be a trivial change.  Import System.ComponentModel here too.

   1: ''' <summary>
   2: ''' Get the Description of an enum value
   3: ''' </summary>
   4: ''' <param name="value"></param>
   5: ''' <returns></returns>
   6: ''' <remarks>If there is no description attribute, the name will be used.</remarks>
   7: Public Function GetDescription(ByVal value As System.Enum) As String
   8:   Return GetDescription(value.GetType().GetField(value.ToString))
   9: End Function
  10:  
  11: ''' <summary>
  12: ''' Get the description of a field
  13: ''' </summary>
  14: ''' <param name="field"></param>
  15: ''' <returns></returns>
  16: ''' <remarks>If the field doesn't have a description attribute, the name is used</remarks>
  17: Public Function GetDescription(ByVal field As System.Reflection.FieldInfo)
  18:   ' Get the array of description attributes applied (there will be 0 or 1)
  19:   Dim descriptions() As Object
  20:     descriptions = field.GetCustomAttributes(GetType(DescriptionAttribute), False)
  21:  
  22:   ' If there was a description, return it
  23:   If descriptions.Length > 0 Then
  24:   Return DirectCast(descriptions(0), DescriptionAttribute).Description
  25:   End If
  26:  
  27:   ' Otherwise return the field's name
  28:   Return field.Name
  29: End Function

Now that we can easily grab the description from an enum field, let's build ourselves a new dropdown control.

   1: Imports System.Reflection
   2:  
   3: ''' <summary>
   4: ''' A custom dropdown that binds to an enum
   5: ''' </summary>
   6: ''' <remarks>
   7: ''' The DataSource is set to an Enum type name, not a collection,
   8: ''' and it can be set within the markup if desired
   9: ''' <example><![CDATA[
  10: '''     <app:EnumList Runat="server" ID="ddlRegion" DataSource="MyApp.RegionCode" />
  11: '''     ...
  12: '''     ddlRegion.DataBind()
  13: '''     ddlRegion.SelectedValue = RegionCode.West
  14: ''' ]]></example>
  15: ''' </remarks>
  16: Public Class EnumList
  17:   Inherits System.Web.UI.WebControls.DropDownList
  18:  
  19:   Private _enumType As Type
  20:   ''' <summary>
  21:   ''' Specify the enum type that we'll bind to
  22:   ''' </summary>
  23:   ''' <value></value>
  24:   ''' <returns></returns>
  25:   ''' <remarks></remarks>
  26:   Public Shadows Property DataSource() As String
  27:   Get
  28:   Return _enumType.ToString
  29:   End Get
  30:   Set(ByVal value As String)
  31:             _enumType = System.Type.GetType(value, True, True)
  32:   End Set
  33:   End Property
  34:  
  35:   ''' <summary>
  36:   ''' Replace SelectedValue with an Enum-based version
  37:   ''' </summary>
  38:   ''' <value></value>
  39:   ''' <returns></returns>
  40:   ''' <remarks></remarks>
  41:   Public Shadows Property SelectedValue() As System.Enum
  42:   Get
  43:   ' Get the value from the request to allow for disabled viewstate
  44:   Dim RequestValue As String = Me.Page.Request.Params(Me.UniqueID)
  45:  
  46:   Return System.Enum.Parse(_enumType, RequestValue)
  47:   End Get
  48:   Set(ByVal value As System.Enum)
  49:   MyBase.SelectedValue = value.ToString
  50:   End Set
  51:   End Property
  52:  
  53:   ''' <summary>
  54:   ''' Replace DataBind so that we can bind to the list of fields in the
  55:   ''' enum.  Call our GetDescription function for each one to get the
  56:   ''' text to display.
  57:   ''' </summary>
  58:   ''' <remarks></remarks>
  59:   Public Overrides Sub DataBind()
  60:   Me.Items.Clear()
  61:   Dim flags As BindingFlags = BindingFlags.Public Or BindingFlags.Static
  62:  
  63:   For Each field As FieldInfo In _enumType.GetFields(flags)
  64:   Me.Items.Add(New ListItem(GetDescription(field), field.Name))
  65:   Next
  66:   End Sub
  67:  
  68: End Class

The usage is quite simple, as the example in the comment shows.  Here's the markup for the control:

   1: <app:EnumList Runat="server" ID="ddlRegion" DataSource="MyApp.RegionCode" />

And in the code-behind, it's wired up as follows:

   1: ddlRegion.DataBind()
   2: ddlRegion.SelectedValue = RegionCode.West

Here's the result.  I chose to have the value of the dropdown be the enum field name rather than its value, because that felt cleaner.

image 

   1: <select name="dnn$ctr396$ManageJobs$ddlRegion" id="dnn_ctr396_ManageJobs_ddlRegion">
   2:   <option value="NotSelected">-- Select --</option>
   3:   <option value="Midwest">US - Midwest</option>
   4:   <option value="Northeast">US - Northeast</option>
   5:   <option value="Southeast">US - Southeast</option>
   6:   <option value="Southwest">US - Southwest</option>
   7:   <option selected="selected" value="West">US - West</option>
   8:   <option value="Canada">Canada</option>
   9: </select>

I suspect that there are going to be some goofy bugs in here if this was hit with some complex scenarios.  But fortunately for me, this project won't have any complex scenarios.