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.
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.