in

Platinum Bay

Peace, Love, Team System, and Community

This Blog

Syndication


.NETicated

Enumeration-Based Dropdowns in .NET MVC

I have a few enumerations in DevEvents, and I found myself frequently writing code similar to the following:

Dictionary<string, int> etlist = new Dictionary<string, int>();
etlist.Add(
"Code Camp", (int)EventType.CodeCamp);
etlist.Add(
"Conference", (int)EventType.Conference);
etlist.Add(
"Day of .NET", (int)EventType.DayOfDotNet);
etlist.Add(
"Hands-On Labs", (int)EventType.HandsOnLabs);
etlist.Add(
"User Group Meeting", (int)EventType.UserGroup);
etlist.Add(
"Workshop", (int)EventType.Workshop);
etlist.Add(
"Pub Night", (int)EventType.PubNight);
etlist.Add(
"Online", (int)DevEvents.Common.EventType.Online);
etlist.Add(
"BarCamp", (int)DevEvents.Common.EventType.BarCamp);
etlist.Add(
"Geek Dinner", (int)DevEvents.Common.EventType.GeekDinner);

if (evnt != null)
ViewData[
"EventTypes"] = new SelectList(etlist, "Value", "Key", evnt.EventTypeID);
else
ViewData["EventTypes"] = new SelectList(etlist, "Value", "Key");

Update - 1/12/2009: For an easier way to do the above, check out this post by Rune Jacobson.

The problem with the approach however, is that every time I need to add a new enumeration value, I would have to modify each of these code sections, and that got very old very fast. I needed a way to generate dropdowns from the enumerations themselves. I ended up with a custom HtmlHelper to generate the dropdowns and eliminate the repetitive code.

First, I created the base HtmlHelper:

public static string EnumDropDown(this HtmlHelper helper, Type enumType, string id)

The ID is used to set the HTML ID attribute, since some dropdowns were being used by jQuery. The parameter enumType is used to pass in the type of enumeration that the dropdown should be generated from. Since I need it to be an enumeration, I check as such, returning an empty string if the check fails.

if (enumType == null || !enumType.IsEnum)
return "";

Next, I attempt to retrieve a value to set as default; first from ViewData, and then from the model if it has been set. I catch an empty exception at the end, which is typically a bad practice, because it is not important if the value cannot be found – the View may be an empty form.

object defaultValue = null;

if (helper.ViewData != null)
defaultValue = helper.ViewData.Eval(id);

try
{
if (defaultValue == null)
defaultValue = helper.ViewData.Model.GetType().GetProperty(id).GetValue(helper.ViewData.Model,
null);
}
catch (Exception) { }

Finally, I set about to return HTML back to the View for the desired dropdown. I create a StringBuilder, append the select start tag, and iterate through all the fields on the enumeration:

StringBuilder sb = new StringBuilder();

sb.AppendFormat(
"<select id=\"{0}\">", id);
foreach (var item in enumType.GetFields(BindingFlags.Public | BindingFlags.Static))
{

Next, for each enumeration field, I check whether the value equals the expected default value determined above:

string def = "";
if (defaultValue != null && defaultValue.ToString() == item.GetRawConstantValue().ToString())
def =
"selected=\"selected\"";

Finally, I append each option to the StringBuilder:

    sb.AppendFormat("<option value=\"{0}\" {1}>{2}</option>",
item.GetRawConstantValue().ToString(), def, item.Name);
}
sb.Append(
"</select>");

return sb.ToString();

I was then able to call my HtmlHelper from the View as follows:

<%= Html.EnumDropDown(typeof(DevEvents.Common.EventType), "EventTypeID") %>

I ran the code, and had a dropdown rendered with each item in the enumeration. There was one small problem however; each option display value was the enumeration field name, exactly. Instead of seeing a pretty name like “Hands-On Labs”, I saw “HandsOnLabs”. Of course this is to be expected based on the generation method used, but how do I enable a more elegant UX?

The solution I used was to create a custom Attribute for my custom enumerations, storing a single property called DisplayName:

[AttributeUsage(AttributeTargets.Field)]
public class EnumDisplayNameAttribute : Attribute
{
private string _displayName;

public EnumDisplayNameAttribute(string displayName)
{
_displayName = displayName;
}

public string DisplayName
{
get
{
return _displayName;
}
}
}

I then applied that attribute to my custom enumerations as follows:

public enum EventType
{
[
EnumDisplayName("User Group")]
UserGroup = 0,
[
EnumDisplayName("Code Camp")]
CodeCamp = 1,
[
EnumDisplayName("Day of .NET")]
DayOfDotNet = 2,
[
EnumDisplayName("Conference")]
Conference = 3,
[
EnumDisplayName("Hands-On Lab")]
HandsOnLabs = 4,
[
EnumDisplayName("Workshop")]
Workshop = 5,
[
EnumDisplayName("Pub Night")]
PubNight = 6,
[
EnumDisplayName("Online")]
Online = 7,
[
EnumDisplayName("BarCamp")]
BarCamp = 8,
[
EnumDisplayName("Geek Dinner")]
GeekDinner = 9
}

Finally, in my HtmlHelper, I checked for the presence of this attribute on each enumeration field, and set the display name accordingly:

EnumDisplayNameAttribute[] attributes =
(
EnumDisplayNameAttribute[])item.GetCustomAttributes(typeof(EnumDisplayNameAttribute), false);

if (attributes.Length > 0)
sb.AppendFormat(
"<option value=\"{0}\" {1}>{2}</option>",
item.GetRawConstantValue().ToString(), def, attributes[0].DisplayName);
else
sb.AppendFormat("<option value=\"{0}\" {1}>{2}</option>",
item.GetRawConstantValue().ToString(), def, item.Name);

Here is the final completed code for the HtmlHelper:

public static string EnumDropDown(this HtmlHelper helper, Type enumType, string id)
{
if (enumType == null || !enumType.IsEnum)
return "";

object defaultValue = null;

if (helper.ViewData != null)
defaultValue = helper.ViewData.Eval(id);

try
{
if (defaultValue == null)
defaultValue = helper.ViewData.Model.GetType().GetProperty(id).GetValue(helper.ViewData.Model,
null);
}
catch (Exception) { }

StringBuilder sb = new StringBuilder();

sb.AppendFormat(
"<select id=\"{0}\">", id);
foreach (var item in enumType.GetFields(BindingFlags.Public | BindingFlags.Static))
{
string def = "";
if (defaultValue != null && defaultValue.ToString() == item.GetRawConstantValue().ToString())
def =
"selected=\"selected\"";

EnumDisplayNameAttribute[] attributes =
(
EnumDisplayNameAttribute[])item.GetCustomAttributes(typeof(EnumDisplayNameAttribute), false);

if (attributes.Length > 0)
sb.AppendFormat(
"<option value=\"{0}\" {1}>{2}</option>",
item.GetRawConstantValue().ToString(), def, attributes[0].DisplayName);
else
sb.AppendFormat("<option value=\"{0}\" {1}>{2}</option>",
item.GetRawConstantValue().ToString(), def, item.Name);
}
sb.Append(
"</select>");

return sb.ToString();
}

What do you think? Would you have done it differently?

Updated:

In thinking about this, the next logical step would be to store the enumeration values in the database so they could be updated at runtime. While I’m not going to set about to do this right away, I would imagine pulling them when the application starts and caching them using a SQL cache dependency. Not too many things should be cached in application memory, but this list should stay relatively small. Thoughts?

Published Jan 10 2009, 02:47 AM by Steve
Filed under:

Comments

January 10, 2009 7:30 AM

Link Listing - January 10, 2009

 

January 10, 2009 7:31 AM

Code Camps First New Hampshire Code Camp on February 28th [Via: cbowen ] Link Collections Interesting...

 

January 10, 2009 2:28 PM

Pingback from  Dew Drop - January 10, 2009 | Alvin Ashcraft's Morning Dew

 

January 10, 2009 9:17 PM

Hi Steve,

It's nice to see someone working with the raw HTML controls.   I thought I was the only one that did that.  The way you work with them is really nice.

Good work!

Phil
 

January 11, 2009 9:58 AM

Pingback from  Arjan`s World

 

January 11, 2009 11:51 PM

Hey,

I bumped into the same, but wanted to use the built-in Html.DropDownList. Turns out this was pretty easy;

slothonline.blogspot.com/.../making-dropdownlist-in-aspnet-mvc-from.html

Of course you could easily integrate your solution for more human-readable text values in that as well. :)

 

January 19, 2009 4:58 PM

Thank you for submitting this cool story - Trackback from DotNetShoutout

 

March 30, 2009 12:45 PM

Rune, The problem I found with the slothonline method is that it automatically sorts the enum by it's value.  Is there a method as easy as that, that will keep the enum in the physical order you have it in the code?

Jeremy
 

Leave a Comment

(required )  
(optional )
(required )  
Add

About Steve

Steve Andrews is a Team System MVP and INETA Speaker, and has been working as a developer for more than 9 years. During this time, he has designed and developed applications in such widely varying areas as trust accounting, medical information management, supply chain management, and retail systems. Steve is also an MCP, ICSOO, Speaker Liaison for the Philly .NET User Group, and community fanatic.
Powered by Community Server (Commercial Edition), by Telligent Systems
© Platinum Bay | Some Rights Reserved Creative Commons License

Disclaimer: The information in this weblog is provided "AS IS" with no warranties, and confers no rights. This weblog does not represent the thoughts, intentions, plans or strategies of my employer. It is solely my opinion. Feel free to challenge me, disagree with me, or tell me I'm completely nuts in the comments section of each blog entry, but I reserve the right to delete any comment for any reason whatsoever (abusive, profane, rude, or annonymous comments) - so keep it polite, please.