MVC 3 helper for radiobuttonlist enum - asp.net-mvc-3

I need some help. I'm trying to build a view where I need groups of radiobuttons of enum types.
I have several enum types(classes) like this:
[DataContract(Namespace = Constants.SomeDataContractNamespace)]
public enum OneEnumDataContract
{
[Display(Name = "Text_None", Description = "Text_None", ResourceType = typeof(TextResource))]
[EnumMember]
None = 0,
[Display(Name = "Text_Medium", Description = "Text_Medium", ResourceType = typeof(TextResource))]
[EnumMember]
Medium = 1,
[Display(Name = "Text_Very", Description = "Text_Very", ResourceType = typeof(TextResource))]
[EnumMember]
Very = 2
}
In my model(a datacontract, using WCF) I have this property for the enum datacontract:
[DataMember(Order = 23)]
[Display(Name = "EnumValue", Description = "EnumValue_Description", ResourceType = typeof(TextResource))]
public OneEnumDataContract EnumClass1 { get; set; }
In my view I would try to make the group of radiobuttons like this(with a helper):
#Html.RadioButtonListEnum("EnumList1", Model.EnumClass1)
My helper:
public static MvcHtmlString RadioButtonListEnum<TModel>(this HtmlHelper<TModel> helper, string NameOfList, object RadioOptions)
{
StringBuilder sb = new StringBuilder();
//som other code for pairing with resourcefile...
foreach(var myOption in enumTexts.AllKeys)
{
sb.Append("<p>");
sb.Append(enumTexts.GetValues(myOption)[0]);
sb.Append(helper.RadioButton(NameOfList, System.Convert.ToInt16(myOption)));
sb.Append("</p>");
}
return MvcHtmlString.Create(sb.ToString());
}
This gives me the first enumvalue in OneEnumDataContract, None, as the parameter RadioOptions.
How can I get all the enumvalues in the datacontract into the helper?

This is one I created recently. It won't work if you try it on a non-enum but works for my enum needs. I copied bit's and pieces from different DropDownList helpers like nikeaa posted.
#region RadioButtonList
public static MvcHtmlString RadioButtonListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IDictionary<string, object> htmlAttributes = null) where TModel : class
{
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
String field = ExpressionHelper.GetExpressionText(expression);
String fieldname = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(field);
var inputName = fieldname;
TProperty val = GetValue(htmlHelper, expression);
var divTag = new TagBuilder("div");
divTag.MergeAttribute("id", inputName);
divTag.MergeAttribute("class", "radio");
foreach (var item in Enum.GetValues(val.GetType()))
{
DisplayAttribute[] attr = (DisplayAttribute[])item.GetType().GetField(item.ToString()).GetCustomAttributes(typeof(DisplayAttribute), true);
if (attr == null || attr.Length == 0 || attr[0].Name != null)
{
string name = attr != null && attr.Length > 0 && !string.IsNullOrWhiteSpace(attr[0].Name) ? attr[0].Name : item.ToString();
var itemval = item;
var radioButtonTag = RadioButton(htmlHelper, inputName, new SelectListItem { Text = name, Value = itemval.ToString(), Selected = val.Equals(itemval) }, htmlAttributes);
divTag.InnerHtml += radioButtonTag;
}
}
return new MvcHtmlString(divTag.ToString());
}
public static string RadioButton(this HtmlHelper htmlHelper, string name, SelectListItem listItem,
IDictionary<string, object> htmlAttributes)
{
var inputIdSb = new StringBuilder();
inputIdSb.Append(name)
.Append("_")
.Append(listItem.Value);
var sb = new StringBuilder();
var builder = new TagBuilder("input");
if (listItem.Selected) builder.MergeAttribute("checked", "checked");
builder.MergeAttribute("type", "radio");
builder.MergeAttribute("value", listItem.Value);
builder.MergeAttribute("id", inputIdSb.ToString());
builder.MergeAttribute("name", name);
builder.MergeAttributes(htmlAttributes);
sb.Append(builder.ToString(TagRenderMode.SelfClosing));
sb.Append(RadioButtonLabel(inputIdSb.ToString(), listItem.Text, htmlAttributes));
sb.Append("<br>");
return sb.ToString();
}
public static string RadioButtonLabel(string inputId, string displayText,
IDictionary<string, object> htmlAttributes)
{
var labelBuilder = new TagBuilder("label");
labelBuilder.MergeAttribute("for", inputId);
labelBuilder.MergeAttributes(htmlAttributes);
labelBuilder.InnerHtml = displayText;
return labelBuilder.ToString(TagRenderMode.Normal);
}
public static TProperty GetValue<TModel, TProperty>(HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression) where TModel : class
{
TModel model = htmlHelper.ViewData.Model;
if (model == null)
{
return default(TProperty);
}
Func<TModel, TProperty> func = expression.Compile();
return func(model);
}
#endregion
I use it like this
#Html.RadioButtonListFor(m => m.PlayFormat)
You may need to more code to set the correct element name for more complicated uses.
If the enum items have a Display attribute, the name is displayed. Otherwise the enum item is displayed. If the Display name is null, that value is not shown as an option. In this enum, "None" isn't displayed, "Singles" is displayed from the enum value, "Men's Doubles" and all the other's have text from [Display(Name="Men's Doubles")]
public enum PlayFormat
{
[Display(Name=null)]
None = 0,
Singles = 1,
[Display(Name = "Men's Doubles")]
MenDoubles = 2,
[Display(Name = "Women's Doubles")]
WomenDoubles = 3,
[Display(Name = "Mixed Doubles")]
MixedDoubles = 4,
[Display(Name = "Men's Group")]
MenGroup = 5,
[Display(Name = "Women's Group")]
WomenGroup = 6,
[Display(Name = "Mixed Group")]
MixedGroup = 7
}
The page looks like this (except each - is a radio button)
- Singles
- Men's Doubles
- Women's Doubles
- Mixed Doubles
- Men's Group
- Women's Group
- Mixed Group

Here is a helper method that I found on the internet to make a drop down list from an enum. You should be able to modify this code to create radio buttons instead of a drop down.
namespace Localicy.HtmlHelpers {
public static class HtmlHelperExtensions {
private static Type GetNonNullableModelType(ModelMetadata modelMetadata) {
Type realModelType = modelMetadata.ModelType;
Type underlyingType = Nullable.GetUnderlyingType(realModelType);
if (underlyingType != null)
realModelType = underlyingType;
return realModelType;
}
private static readonly SelectListItem[] SingleEmptyItem = new[] { new SelectListItem { Text = "", Value = "" } };
public static string GetEnumDescription<TEnum>(TEnum value) {
FieldInfo fi = value.GetType().GetField(value.ToString());
DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if ((attributes != null) && (attributes.Length > 0))
return attributes[0].Description;
else
return value.ToString();
}
public static MvcHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression) {
return EnumDropDownListFor(htmlHelper, expression, null, null);
}
public static MvcHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression, string defaultValueText) {
return EnumDropDownListFor(htmlHelper, expression, defaultValueText, null);
}
public static MvcHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression, string defaultValueText, object htmlAttributes) {
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
Type enumType = GetNonNullableModelType(metadata);
IEnumerable<TEnum> values = Enum.GetValues(enumType).Cast<TEnum>();
IEnumerable<SelectListItem> items = from value in values
select new SelectListItem {
Text = GetEnumDescription(value),
Value = value.ToString(),
Selected = value.Equals(metadata.Model)
};
// If the enum is nullable, add an 'empty' item to the collection
if (metadata.IsNullableValueType || defaultValueText != null)
if(defaultValueText != null) {
SelectListItem[] tempItem = new[] { new SelectListItem { Text = defaultValueText, Value = "" } };
items = tempItem.Concat(items);
}
else
items = SingleEmptyItem.Concat(items);
//items = (new ).Concat(items)
return htmlHelper.DropDownListFor(expression, items, htmlAttributes);
}
}
}

Related

Extending MVC RadioButtonFor using HtmlHelper extensions

I'm trying to build an HtmlHelper to add functionality to an MVC RadioButtonFor control. I simply want to add a class and style attribute to the radio button given certain conditions. I can't seem to get my extension to include attributes. I've followed several examples I've found online, but no luck.
Here is my code:
public static MvcHtmlString GCSRadioButtonForHelper<TModel>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, bool?>> expression,
Object htmlAttributes,
object value )
{
bool isRequired = false;
bool isHidden = false;
object styleList;
object classList;
Dictionary<string, object> attributes = new Dictionary<string, object>();
attributes.TryGetValue("class", out classList);
attributes.TryGetValue("style", out styleList);
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var categoryName = string.Empty;
if (metadata.ContainerType != null)
categoryName = metadata.ContainerType.Name + "." + metadata.PropertyName;
isHidden = GetIsHidden(metadata);
isRequired = GetIsRequired(metadata);
if (isHidden)
classList = classList + " GCSHidden ";
if (isRequired)
{
classList = classList + " GCSRequired ";
}
attributes.Remove("class");
attributes.Remove("style");
attributes.Add("class", classList);
attributes.Add("style", styleList);
//Render HTML String
MvcHtmlString html = System.Web.Mvc.Html.InputExtensions.RadioButtonFor(htmlHelper, expression, attributes, value);
return html;
}
The problem is that the result of InputExtensions.RadioButtonFor() never includes the attributes class or style as I have specified. Any ideas?
TIA

Enum [Display(Name) localisation MVC 4

I have a enum that holds countries:
public enum CountryEnum
{
[Display(Name = "AF", ResourceType = typeof(Global))]
AF,
[Display(Name = "AL", ResourceType = typeof(Global))]
AL,
[Display(Name = "DZ", ResourceType = typeof(Global))]
DZ,
};
As you can see I make use of the DataAnnotations to localise the values.
Now I want to display a dropdownlist with all of the localised country names. I come up with this code:
public static string GetDisplayName<TEnum>(TEnum value)
{
FieldInfo fi = value.GetType().GetField(value.ToString());
DisplayAttribute[] attributes =
(DisplayAttribute[])fi.GetCustomAttributes(
typeof(DisplayAttribute), false);
if ((attributes != null) && (attributes.Length > 0))
return attributes[0].Name;
else
return value.ToString();
}
I have a Html helper that makes use of the above method:
public static MvcHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression, object htmlAttributes)
{
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
Type enumType = GetNonNullableModelType(metadata);
IEnumerable<TEnum> values = Enum.GetValues(enumType).Cast<TEnum>();
IEnumerable<SelectListItem> items = from value in values
select new SelectListItem
{
Text = GetDisplayName(value),
Value = value.ToString(),
Selected = value.Equals(metadata.Model)
};
// If the enum is nullable, add an 'empty' item to the collection
if (metadata.IsNullableValueType)
items = SingleEmptyItem.Concat(items);
return htmlHelper.DropDownListFor(expression, items, htmlAttributes);
}
The DropDown renders correctly, however, GetDisplayName doesn't return the localised value, it just display the name attribute (e.g. AF for the first entry).
How can I modify GetDisplayName method to return the localised value?
You need to update your GetDisplayName method to use the GetName() method rather than the Name property of the DisplayAttribute.
Like this:
public static string GetDisplayName<TEnum>(TEnum value)
{
FieldInfo fi = value.GetType().GetField(value.ToString());
DisplayAttribute[] attributes = (DisplayAttribute[])fi.
GetCustomAttributes(typeof(DisplayAttribute), false);
if ((attributes != null) && (attributes.Length > 0))
return attributes[0].GetName();
else
return value.ToString();
}
From the MSDN documentation for DisplayAttribute.Name:
Do not use this property to get the value of the Name property. Use
the GetName method instead.
The MSDN documentation for the GetName() method has this to say:
Returns the localized string for the Name property, if the
ResourceType property has been specified and the Name property
represents a resource key; otherwise, the non-localized value of the
Name property.
I was having a similar problem, having setup the Display Attribute as you did (easier since classes use the same attribute to load localizations), so took your initial code and tweaked it a bit, now it is displaying the localization string as expected. I don't think it is the most clever or optimized way of doing it, but it works. Hopefully that's what you intended.
public static string GetDisplayName<TEnum>(TEnum value)
{
FieldInfo fi = value.GetType().GetField(value.ToString());
DisplayAttribute[] attributes = (DisplayAttribute[])fi.
GetCustomAttributes(typeof(DisplayAttribute), false);
if ((attributes != null) && (attributes.Length > 0))
{
string key = attributes[0].Name;
string localizedString = attributes[0].ResourceType.GetProperty(key).GetValue("").ToString();
return localizedString;
}
else
return value.ToString();
}

Localized enum strings in SelectList

In my MVC3 app. I'm using select list to populate combo box with enum values like this
<div class="editor-field">
#Html.DropDownListFor(x => x.AdType, new SelectList(Enum.GetValues(typeof(MyDomain.Property.AdTypeEnum))), " ", new { #class = "dxeButtonEdit_Glass" })
</div>
MyDomain.Property looks like this
public enum AdTypeEnum
{
Sale = 1,
Rent = 2,
SaleOrRent = 3
};
How can I use my localized strings to localize these enums?
You could write a custom attribute:
public class LocalizedNameAttribute: Attribute
{
private readonly Type _resourceType;
private readonly string _resourceKey;
public LocalizedNameAttribute(string resourceKey, Type resourceType)
{
_resourceType = resourceType;
_resourceKey = resourceKey;
DisplayName = (string)_resourceType
.GetProperty(_resourceKey, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)
.GetValue(null, null);
}
public string DisplayName { get; private set; }
}
and a custom DropDownListForEnum helper:
public static class DropDownListExtensions
{
public static IHtmlString DropDownListForEnum<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
string optionLabel,
object htmlAttributes
)
{
if (!typeof(TProperty).IsEnum)
{
throw new Exception("This helper can be used only with enum types");
}
var enumType = typeof(TProperty);
var fields = enumType.GetFields(
BindingFlags.Static | BindingFlags.GetField | BindingFlags.Public
);
var values = Enum.GetValues(enumType).OfType<TProperty>();
var items =
from value in values
from field in fields
let descriptionAttribute = field
.GetCustomAttributes(
typeof(LocalizedNameAttribute), true
)
.OfType<LocalizedNameAttribute>()
.FirstOrDefault()
let displayName = (descriptionAttribute != null)
? descriptionAttribute.DisplayName
: value.ToString()
where value.ToString() == field.Name
select new { Id = value, Name = displayName };
var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var enumObj = metadata;
var selectList = new SelectList(items, "Id", "Name", metadata.Model);
return htmlHelper.DropDownListFor(expression, selectList, optionLabel, htmlAttributes);
}
}
And then it's easy:
Model:
public enum AdTypeEnum
{
[LocalizedName("Sale", typeof(Messages))]
Sale = 1,
[LocalizedName("Rent", typeof(Messages))]
Rent = 2,
[LocalizedName("SaleOrRent", typeof(Messages))]
SaleOrRent = 3
}
public class MyViewModel
{
public AdTypeEnum AdType { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new MyViewModel
{
AdType = AdTypeEnum.SaleOrRent
});
}
}
View:
#model MyViewModel
#Html.DropDownListForEnum(
x => x.AdType,
" ",
new { #class = "foo" }
)
Finally you should create a Messages.resx file which will contain the necessary keys (Sale, Rent and SaleOrRent). And if you want to localize you simply define a Messages.xx-XX.resx with the same keys for a different culture and swap the current thread culture.

MVC 3: Enum dropdown template for use in EditorForModel

I know there is several threads on this topic. I see a lot of solutions but most of them demands the knowledge of which type the enum is. Since Enum by default renders uses the template for string, it's more difficult to find out the exact type.
Has someone got a complete example on how to solve this?
Edit. Based on Roys suggestion, here's my code.
HtmlHelpers.cs
public static MvcHtmlString EnumDropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression) where TModel : class
{
var type = htmlHelper.ViewData.ModelMetadata.ModelType;
var inputName = type.Name;
var value = htmlHelper.ViewData.Model == null ? default(TProperty) : expression.Compile()(htmlHelper.ViewData.Model);
var selected = value == null ? String.Empty : value.ToString();
return htmlHelper.DropDownList(inputName, ToSelectList(type, selected));
}
private static IEnumerable<SelectListItem> ToSelectList(Type enumType, string selectedItem)
{
var items = new List<SelectListItem>();
foreach (var item in Enum.GetValues(enumType))
{
var fi = enumType.GetField(item.ToString());
var attribute = fi.GetCustomAttributes(typeof(DescriptionAttribute), true).FirstOrDefault();
var title = attribute == null ? item.ToString() : ((DescriptionAttribute)attribute).Description;
var listItem = new SelectListItem
{
Value = ((int)item).ToString(),
Text = title,
Selected = selectedItem == ((int)item).ToString()
};
items.Add(listItem);
}
return new SelectList(items, "Value", "Text");
}
Viewmodel
public class AddTestForm
{
[UIHint("Enum")]
public EnumType Type { get; set; }
public string Description { get; set; }
public enum EnumType
{
One = 1,
Two = 2
}
EditorTemplates/Enum.cshtml
#model object
#Html.EnumDropDownListFor(x => x)
And in views...
#Html.EditorForModel()
The problem now is that I can't get the enum to fill after the form has been posted.
Your helper generates wrong names to the generated select elements:
<select id="Type_EnumType" name="Type.EnumType">
The DefaultModelBinder uses the name attribute to match the property names. Your helper generated name "Type.EnumType" doesn't match with the Model property name "Type". To make it work instead of the Type name you need to get the property name from the expression. It's quite easy with the ExpressionHelper class:
public static MvcHtmlString EnumDropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression) where TModel : class
{
...
return htmlHelper.DropDownList(ExpressionHelper.GetExpressionText(expression), ToSelectList(type, selected));
}

Fun (?) with Linq Expressions in extension methods

I wrote an HtmlHelper expression I use a lot of the time to put title tags into my dropdown lists like so:
public static HtmlString SelectFor<TModel, TProperty, TListItem>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<TListItem> enumeratedItems,
string idPropertyName,
string displayPropertyName,
string titlePropertyName,
object htmlAttributes) where TModel : class
{
//initialize values
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var propertyName = metaData.PropertyName;
var propertyValue = htmlHelper.ViewData.Eval(propertyName).ToStringOrEmpty();
var enumeratedType = typeof(TListItem);
//build the select tag
var returnText = string.Format("<select id=\"{0}\" name=\"{0}\"", HttpUtility.HtmlEncode(propertyName));
if (htmlAttributes != null)
{
foreach (var kvp in htmlAttributes.GetType().GetProperties()
.ToDictionary(p => p.Name, p => p.GetValue(htmlAttributes, null)))
{
returnText += string.Format(" {0}=\"{1}\"", HttpUtility.HtmlEncode(kvp.Key),
HttpUtility.HtmlEncode(kvp.Value.ToStringOrEmpty()));
}
}
returnText += ">\n";
//build the options tags
foreach (TListItem listItem in enumeratedItems)
{
var idValue = enumeratedType.GetProperties()
.FirstOrDefault(p => p.Name == idPropertyName)
.GetValue(listItem, null).ToStringOrEmpty();
var titleValue = enumeratedType.GetProperties()
.FirstOrDefault(p => p.Name == titlePropertyName)
.GetValue(listItem, null).ToStringOrEmpty();
var displayValue = enumeratedType.GetProperties()
.FirstOrDefault(p => p.Name == displayPropertyName)
.GetValue(listItem, null).ToStringOrEmpty();
returnText += string.Format("<option value=\"{0}\" title=\"{1}\"",
HttpUtility.HtmlEncode(idValue), HttpUtility.HtmlEncode(titleValue));
if (idValue == propertyValue)
{
returnText += " selected=\"selected\"";
}
returnText += string.Format(">{0}</option>\n", displayValue);
}
//close the select tag
returnText += "</select>";
return new HtmlString(returnText);
}
...this works swimmingly, but there are times when I want to go further. I'd like to customize the id, display, and title pieces of this beast without having to write out the html. For example, if I have some classes in a model like so:
public class item
{
public int itemId { get; set; }
public string itemName { get; set; }
public string itemDescription { get; set; }
}
public class model
{
public IEnumerable<item> items { get; set; }
public int itemId { get; set; }
}
In my view I can write:
#Html.SelectFor(m => m.itemId, Model.items, "itemId", "itemName", "itemDescription", null)
...and I'll get a nice dropdown with title attributes etc. This is great as long as the enumerated items have properties exactly as I'd like to display them. But what I'd really like to do is something like:
#Html.SelectFor(m => m.itemId, Model.items, id=>id.itemId, disp=>disp.itemName, title=>title.itemName + " " + title.itemDescription, null)
...and have, in this case, the title attribute on the options be a concatenation of the itemName property and the itemDescription property. I confess the meta-level of lambda expressions and Linq functions has got me a little dizzy. Can someone point me in the right direction?
FINAL RESULT For those who are curious, the following code gives me complete control over the select list's ID, Title, and DisplayText properties using lambda expressions:
public static HtmlString SelectFor<TModel, TProperty, TListItem>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> forExpression,
IEnumerable<TListItem> enumeratedItems,
Attribute<TListItem> idExpression,
Attribute<TListItem> displayExpression,
Attribute<TListItem> titleExpression,
object htmlAttributes,
bool blankFirstLine) where TModel : class
{
//initialize values
var metaData = ModelMetadata.FromLambdaExpression(forExpression, htmlHelper.ViewData);
var propertyName = metaData.PropertyName;
var propertyValue = htmlHelper.ViewData.Eval(propertyName).ToStringOrEmpty();
var enumeratedType = typeof(TListItem);
//build the select tag
var returnText = string.Format("<select id=\"{0}\" name=\"{0}\"", HttpUtility.HtmlEncode(propertyName));
if (htmlAttributes != null)
{
foreach (var kvp in htmlAttributes.GetType().GetProperties()
.ToDictionary(p => p.Name, p => p.GetValue(htmlAttributes, null)))
{
returnText += string.Format(" {0}=\"{1}\"", HttpUtility.HtmlEncode(kvp.Key),
HttpUtility.HtmlEncode(kvp.Value.ToStringOrEmpty()));
}
}
returnText += ">\n";
if (blankFirstLine)
{
returnText += "<option value=\"\"></option>";
}
//build the options tags
foreach (TListItem listItem in enumeratedItems)
{
var idValue = idExpression(listItem).ToStringOrEmpty();
var displayValue = displayExpression(listItem).ToStringOrEmpty();
var titleValue = titleExpression(listItem).ToStringOrEmpty();
returnText += string.Format("<option value=\"{0}\" title=\"{1}\"",
HttpUtility.HtmlEncode(idValue), HttpUtility.HtmlEncode(titleValue));
if (idValue == propertyValue)
{
returnText += " selected=\"selected\"";
}
returnText += string.Format(">{0}</option>\n", displayValue);
}
//close the select tag
returnText += "</select>";
return new HtmlString(returnText);
}
public delegate object Attribute<T>(T listItem);
If you don't need the title attribute on individual options your code could be simplified to:
public static HtmlString SelectFor<TModel, TProperty, TIdProperty, TDisplayProperty, TListItem>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<TListItem> enumeratedItems,
Expression<Func<TListItem, TIdProperty>> idProperty,
Expression<Func<TListItem, TDisplayProperty>> displayProperty,
object htmlAttributes
) where TModel : class
{
var id = (idProperty.Body as MemberExpression).Member.Name;
var display = (displayProperty.Body as MemberExpression).Member.Name;
var selectList = new SelectList(enumeratedItems, id, display);
var attributes = new RouteValueDictionary(htmlAttributes);
return htmlHelper.DropDownListFor(expression, selectList, attributes);
}
and used like this:
#Html.SelectFor(
m => m.itemId,
Model.items,
id => id.itemId,
disp => disp.itemName,
null
)
And if you need the title attribute, well, you will have to implement everything that the DropDownList helper does manually which could be quite of a pain. Here's an example of only a small portion of all the functionality:
public static class HtmlExtensions
{
private class MySelectListItem : SelectListItem
{
public string Title { get; set; }
}
public static HtmlString SelectFor<TModel, TProperty, TIdProperty, TDisplayProperty, TListItem>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<TListItem> enumeratedItems,
Expression<Func<TListItem, TIdProperty>> idProperty,
Expression<Func<TListItem, TDisplayProperty>> displayProperty,
Func<TListItem, string> titleProperty,
object htmlAttributes
) where TModel : class
{
var name = ExpressionHelper.GetExpressionText(expression);
var fullHtmlName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
var select = new TagBuilder("select");
var compiledDisplayProperty = displayProperty.Compile();
var compiledIdProperty = idProperty.Compile();
select.GenerateId(fullHtmlName);
select.MergeAttributes(new RouteValueDictionary(htmlAttributes));
select.Attributes["name"] = fullHtmlName;
var selectedValue = htmlHelper.ViewData.Eval(fullHtmlName);
var options =
from i in enumeratedItems
select ListItemToOption(
ItemToSelectItem(i, selectedValue, compiledIdProperty, compiledDisplayProperty, titleProperty)
);
select.InnerHtml = string.Join(Environment.NewLine, options);
return new HtmlString(select.ToString(TagRenderMode.Normal));
}
private static MySelectListItem ItemToSelectItem<TListItem, TIdProperty, TDisplayProperty>(TListItem i, object selectedValue, Func<TListItem, TIdProperty> idProperty, Func<TListItem, TDisplayProperty> displayProperty, Func<TListItem, string> titleProperty)
{
var value = Convert.ToString(idProperty(i));
return new MySelectListItem
{
Value = value,
Text = Convert.ToString(displayProperty(i)),
Title = titleProperty(i),
Selected = Convert.ToString(selectedValue) == value
};
}
private static string ListItemToOption(MySelectListItem item)
{
var builder = new TagBuilder("option");
builder.Attributes["value"] = item.Value;
builder.Attributes["title"] = item.Title;
builder.SetInnerText(item.Text);
if (item.Selected)
{
builder.Attributes["selected"] = "selected";
}
return builder.ToString();
}
}
and then use like this:
#Html.SelectFor(
m => m.itemId,
Model.items,
id => id.itemId,
disp => disp.itemName,
title => title.itemName + " " + title.itemDescription,
null
)

Resources