Create dropdown list for enum with description C# - c#

I want to create a dropdown list using description of enum instead of its value.
I'd like to know how to get descriptions instead of values in the following code which creates a dropdown list for enum :
public static MvcHtmlString DropDownListForEnum<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression)
{
// get expression property description
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
IEnumerable<TEnum> values = Enum.GetValues(typeof(TEnum)).Cast<TEnum>();
IEnumerable<SelectListItem> items =
values.Select(value => new SelectListItem
{
Text = value.ToString(),
Value = value.ToString(),
Selected = value.Equals(metadata.Model)
});
return htmlHelper.DropDownListFor(
expression,
items
);
}

First, make a new method to get the description like shown below:
public static string GetDescription<T>(string value)
{
Type type = typeof(T);
if (typeof(T).IsGenericType && typeof(T).GetGenericTypeDefinition() == typeof(Nullable<>))
{
type = Nullable.GetUnderlyingType(type);
}
T enumerator = (T)Enum.Parse(type, value);
FieldInfo fi = enumerator.GetType().GetField(enumerator.ToString());
DescriptionAttribute[] attributtes =
(DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributtes != null && attributtes.Length > 0)
return attributtes[0].Description;
else
return enumerator.ToString();
}
And then use it in your helper:
public static MvcHtmlString DropDownListForEnum<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression)
{
// get expression property description
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
IEnumerable<TEnum> values = Enum.GetValues(typeof(TEnum)).Cast<TEnum>();
IEnumerable<SelectListItem> items =
values.Select(value => new SelectListItem
{
Text = value.ToString(),
Value = GetDescription<TEnum>(value.ToString()),
Selected = value.Equals(metadata.Model)
});
return htmlHelper.DropDownListFor(
expression,
items
);
}

Use Enum getnames to get the names
http://msdn.microsoft.com/en-us/library/system.enum.getnames(v=vs.110).aspx

Related

Extend Html.DropDownList with a list

I want to extend the DropDownList to accept any type of list .
The code that I have is for enums. I want to use it for whatever list not only enums.
public static MvcHtmlString DropDownListFor<TModel, TProperty, TEnum>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
TEnum selectedValue)
{
var values = Enum.GetValues(typeof(TEnum)).Cast<TEnum>();
var items = from value in values
select new SelectListItem()
{
Text = value.ToString(),
Value = value.ToString(),
Selected = value.Equals(selectedValue)
};
return SelectExtensions.DropDownListFor(htmlHelper, expression, items);
}
how to refactor this code so it can be used for all type of lists like ( vehicle type, make models, year ..etc)
I got my code working by using the code below :
public static MvcHtmlString DropDownListFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
List<dynamic> selectedValue)
{
var items = from value in selectedValue
select new SelectListItem()
{
Text = value.ToString(),
Value = value.ToString()
};
return htmlHelper.DropDownListFor(expression, items);
}

ListFor enum flags MVC.Net

My model contains an enum with a flags attribute
[Flags()]
public enum InvestmentAmount
{
[Description("£500 - £5,000")]
ZeroToFiveThousand,
[Description("£5,000 - £10,000")]
FiveThousandToTenThousand,
//Deleted remaining entries for size
}
I want to be able to display this in my view as a multiselectable List box.
Obviously the current helper for Listfor() doesn't support enums.
I've tried rolling my own but just receive
The parameter 'expression' must evaluate to an IEnumerable when
multiple selection is allowed.
when it executes.
public static MvcHtmlString EnumListFor<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 = 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)
items = SingleEmptyItem.Concat(items);
RouteValueDictionary htmlattr = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
//htmlattr.Add("multiple", "multiple");
if (expression.GetDescription() != null)
{
htmlattr.Add("data-content", expression.GetDescription());
htmlattr.Add("data-original-title", expression.GetTitle());
htmlattr["class"] = "guidance " + htmlattr["class"];
}
var fieldName = htmlHelper.NameFor(expression).ToString();
return htmlHelper.ListBox(fieldName, items, htmlattr); //Exception thrown here
}
Check out my blog post on a helper method I created to do just this:
http://jnye.co/Posts/4/creating-a-dropdown-list-from-an-enum-in-mvc-and-c%23
This enables you to do something like this:
//If you don't have an enum value use the type
var enumList = EnumHelper.SelectListFor<MyEnum>();
//If you do have an enum value use the value (the value will be marked as selected)
var enumList = EnumHelper.SelectListFor(myEnumValue);
...which you can then use to build your multi list.
The helper class is as follows:
public static class EnumHelper
{
//Creates a SelectList for a nullable enum value
public static SelectList SelectListFor<T>(T? selected)
where T : struct
{
return selected == null ? SelectListFor<T>()
: SelectListFor(selected.Value);
}
//Creates a SelectList for an enum type
public static SelectList SelectListFor<T>() where T : struct
{
Type t = typeof (T);
if (t.IsEnum)
{
var values = Enum.GetValues(typeof(T)).Cast<enum>()
.Select(e => new { Id = Convert.ToInt32(e), Name = e.GetDescription() });
return new SelectList(values, "Id", "Name");
}
return null;
}
//Creates a SelectList for an enum value
public static SelectList SelectListFor<T>(T selected) where T : struct
{
Type t = typeof(T);
if (t.IsEnum)
{
var values = Enum.GetValues(t).Cast<Enum>()
.Select(e => new { Id = Convert.ToInt32(e), Name = e.GetDescription() });
return new SelectList(values, "Id", "Name", Convert.ToInt32(selected));
}
return null;
}
// Get the value of the description attribute if the
// enum has one, otherwise use the value.
public static string GetDescription<TEnum>(this TEnum value)
{
FieldInfo fi = value.GetType().GetField(value.ToString());
if (fi != null)
{
DescriptionAttribute[] attributes =
(DescriptionAttribute[])fi.GetCustomAttributes(
typeof(DescriptionAttribute),
false);
if (attributes.Length > 0)
{
return attributes[0].Description;
}
}
return value.ToString();
}
}
The error appears to have been occurring because I was binding to just an "InvestmentAmount" where as it appears that ListFor checks if the model is a list.
I've had to change my model to a List and build in binding logic (Through AutoMapper) to convert from the flagged Enum to a List and back.
A nicer solution would be to create a generic HTML ListFor helper to do it. Which is the way I'll tackle it if I need it any more than once.

How to add a htmlAttributes in this HtmlHelper?

I'm using right now this code to implement a RadioButtonList using MVC4.
And as you can see, that function does not have htmlAttributes parameter. So I'd like to add it and here is the problem. Check please that the htmlAttributes for RadioButtonFor() is occupied by the id.
I was trying to add it but throws me errors because the id already exists for the loop.
public static class HtmlExtensions
{
public static MvcHtmlString RadioButtonForSelectList<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> listOfValues)
{
return htmlHelper.RadioButtonForSelectList(expression, listOfValues, null);
}
public static MvcHtmlString RadioButtonForSelectList<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> listOfValues,
object htmlAttributes)
{
return htmlHelper.RadioButtonForSelectList(expression, listOfValues, new RouteValueDictionary(htmlAttributes));
}
public static MvcHtmlString RadioButtonForSelectList<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> listOfValues,
IDictionary<string, object> htmlAttributes)
{
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var sb = new StringBuilder();
if (listOfValues != null)
{
foreach (SelectListItem item in listOfValues)
{
var id = string.Format(
"{0}_{1}",
metaData.PropertyName,
item.Value
);
var radio = htmlHelper.RadioButtonFor(expression, item.Value, new { id = id }).ToHtmlString();
sb.AppendFormat(
"{0}<label for=\"{1}\">{2}</label>",
radio,
id,
HttpUtility.HtmlEncode(item.Text)
);
}
}
return MvcHtmlString.Create(sb.ToString());
}
}
In the third method, it looks like the html attributes being passed to the radion button being created is new { id = id }. Try to replace that with the parameter from the method.
UPDATED
Include id in the html attributes and assign a new value to id in each loop iteration.
if (listOfValues != null)
{
if (!htmlAttributes.ContainsKey("id"))
{
htmlAttributes.Add("id", null);
}
foreach (SelectListItem item in listOfValues)
{
var id = string.Format(
"{0}_{1}",
metaData.PropertyName,
item.Value
);
htmlAttributes["id"] = id;
var radio = htmlHelper.RadioButtonFor(expression, item.Value, htmlAttributes).ToHtmlString();
sb.AppendFormat(
"{0}<label for=\"{1}\">{2}</label>",
radio,
id,
HttpUtility.HtmlEncode(item.Text)
);
}
}

How to manipulate values of a tag?

This is different approach to translation of page. It is not meant to be localization.
I created class TransModel which all my ViewModels are inheriting from.
This class fetches string pairs relevant for current ViewModel from database and stores them in "labels" Dictionary. Key for that pair is the value of string here "User Name" and value is translated value.
[Display(Name = "User Name")]
public string UserName { get; set; }
Instead of using Html.LabelFor in View I use extension of it call it TransLabelFor
public static MvcHtmlString TransLabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression,TransModel model)
{
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
string trans = "";
model.labels.TryGetValue(metadata.DisplayName, out trans);
if (trans == null)
{
trans = metadata.DisplayName;
}
return MvcHtmlString.Create(String.Format("<label for='{0}'>{1}</label>", metadata.DisplayName, trans));
}
Now I want to replace what I return by it. I want the original tag as returned by this:
MvcHtmlString originalTag = System.Web.Mvc.Html.LabelExtensions.LabelFor(html, expression);
but with my translation.
Are there any neat ways of doing it instead of string find/replace? I don't like that I have to pass Model around either, any better ideas?
Or "improvement" to your solution (also including reflection, but this will avoid to put TransModel in your helpers)
public static IHtmlString TransLabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
{
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var htmlFieldName = ExpressionHelper.GetExpressionText(expression);
var modelType = typeof (TModel);
var resolvedLabelText = metadata.DisplayName ?? metadata.PropertyName;
if (String.IsNullOrEmpty(resolvedLabelText))
return MvcHtmlString.Empty;
var labelProperty = modelType.GetProperty("labels");
if (labelProperty != null)
{
var labels = labelProperty.GetValue(html.ViewData.Model, null) as Dictionary<string, string>;
if (labels != null && labels.ContainsKey(resolvedLabelText))
resolvedLabelText = labels[resolvedLabelText];
}
var tag = new TagBuilder("label");
tag.Attributes.Add("for", TagBuilder.CreateSanitizedId(html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(htmlFieldName)));
tag.SetInnerText(resolvedLabelText);
return MvcHtmlString.Create(tag.ToString());
}
but this will mean you'll have to change all your LabelFor to TransLableFor (string replace in all solution) : but at least you don't have to add a "TransModel" during the replacement.

MVC3 EnumDropdownList selected value

There are some helpful extension methods for using displaying enums in dropdown lists. For example here and here.
But there is one problem that I encounter, which is that these helpers do not work if the enum is decorated with the Description attribute. The first example works perfectly with the Description attribute, but it doesn't set the selected value. The second example sets the selected value, but it doesn't use the description attribute. So I need to combine both methods into a working helper that does both correctly. I've a lot of variations to get it working but, no success so far. I've tried several ways to create a selectlist, but somehow it ignores the Selected property. In all my tests, the Selected property was set to true on one item, but this property is just ignored.
So any ideas are most welcome!
This is the latest code that I've tried:
public static IEnumerable<SelectListItem> ToSelectList(Type enumType, string selectedItem)
{
List<SelectListItem> items = new List<SelectListItem>();
foreach (var item in Enum.GetValues(enumType))
{
FieldInfo 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 == item.ToString()
};
items.Add(listItem);
}
return items;
}
public static HtmlString EnumDropDownList2For<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> modelExpression)
{
var typeOfProperty = modelExpression.ReturnType;
if (!typeOfProperty.IsEnum)
throw new ArgumentException(string.Format("Type {0} is not an enum", typeOfProperty));
var value = htmlHelper.ViewData.Model == null
? default(TProperty)
: modelExpression.Compile()(htmlHelper.ViewData.Model);
return htmlHelper.DropDownListFor(modelExpression, ToSelectList(modelExpression.ReturnType, value.ToString()));
}
I had the same issue with enums which actually had custom value set
public enum Occupation
{
[Description("Lorry driver")] LorryDriver = 10,
[Description("The big boss")] Director = 11,
[Description("Assistant manager")] AssistantManager = 12
}
What I found is that when I use DropDownListFor(), it doesn't use the selected item from the SelectListItem collection to set the selected option. Instead it selects an option with a value which equals to the property I'm trying to bind to (m => m.Occupation) and for this it uses the enum's .ToString() not the enum's actual integer value. So what I ended up with is setting the SelectListItem's Value like so:
var listItem = new SelectListItem
{
Value = item.ToString(), // use item.ToString() instead
Text = title,
Selected = selectedItem == item.ToString() // <- no need for this
};
The helper method:
public static class SelectListItemsForHelper
{
public static IEnumerable<SelectListItem> SelectListItemsFor<T>(T selected) where T : struct
{
Type t = typeof(T);
if (t.IsEnum)
{
return Enum.GetValues(t).Cast<Enum>().Select(e => new SelectListItem { Value = e.ToString(), Text = e.GetDescription() });
}
return null;
}
public static string GetDescription<TEnum>(this TEnum value)
{
FieldInfo fi = value.GetType().GetField(value.ToString());
if (fi != null)
{
var attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes.Length > 0)
return attributes[0].Description;
}
return value.ToString();
}
}
In the view:
#Html.DropDownListFor(m => m.Occupation, SelectListItemsForHelper.SelectListItemsFor(Model.Occupation), String.Empty)
To summarize the solution that does work (at least for me):
I use the following set of helper methods
public static IEnumerable<SelectListItem> ToSelectList(Type enumType, string selectedItem)
{
List<SelectListItem> items = new List<SelectListItem>();
foreach (var item in Enum.GetValues(enumType))
{
var title = item.GetDescription();
var listItem = new SelectListItem
{
Value = item.ToString(),
Text = title,
Selected = selectedItem == item.ToString()
};
items.Add(listItem);
}
return items;
}
public static MvcHtmlString EnumDropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression) where TModel : class
{
string inputName = GetInputName(expression);
var value = htmlHelper.ViewData.Model == null
? default(TProperty)
: expression.Compile()(htmlHelper.ViewData.Model);
return htmlHelper.DropDownList(inputName, ToSelectList(typeof(TProperty), value.ToString()));
}
public static string GetInputName<TModel, TProperty>(Expression<Func<TModel, TProperty>> expression)
{
if (expression.Body.NodeType == ExpressionType.Call)
{
MethodCallExpression methodCallExpression = (MethodCallExpression)expression.Body;
string name = GetInputName(methodCallExpression);
return name.Substring(expression.Parameters[0].Name.Length + 1);
}
return expression.Body.ToString().Substring(expression.Parameters[0].Name.Length + 1);
}
private static string GetInputName(MethodCallExpression expression)
{
MethodCallExpression methodCallExpression = expression.Object as MethodCallExpression;
if (methodCallExpression != null)
{
return GetInputName(methodCallExpression);
}
return expression.Object.ToString();
}
Usage:
#Html.EnumDropDownListFor(m => m.MyEnumType)
This works for enums with or without a description attribute, and sets the correct selected value.

Categories

Resources