How to get selected text from a SelectList - asp.net-mvc

I am creating a custom helper method for a Drop Down List with the following signature:
public static MvcHtmlString MyCustomDropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList, string optionLabel, object htmlAttributes)
Within the method, I can do the following to get the selected value:
var Value = ((SelectList)selectList).SelectedValue);
In my helper method, I need to find out what the selected text is as well, and not just the value. How will I get that?

If you already have selected value you can just iterate through the IEnumerable trying to find the selected SelectListItem:
SelectListItem[] items = selectList.ToArray();
SelectListItem selectedItem = items.FirstOrDefault(i => i.Value == valueAsString)
?? items[0];
string selectedText = selectedItem.Text;
From my opinion, casting to SelectList is not the best option to find selected value, this casting could throw InvalidCastException when real type of selectList variable is other than SelectList.
In most cases your model contains a value that should be selected in the drop down list. You can get this value using ModelMetadata classes as follows:
//Get value from model or from ModelState
object modelValue = ModelMetadata
.FromLambdaExpression(expression, html.ViewData).Model;
SelectListItem[] items = selectList.ToArray();
string selectedValue = modelValue == null ? null : modelValue.ToString();
SelectListItem selectedItem = items.FirstOrDefault(i => i.Value == selectedValue)
?? items.FirstOrDefault();
string selectedText = selectedItem == null ? null : selectedItem.Text;
//Rest code goes here

public static string getText(SelectList selectList)
{
string text = selectList.Where(x => x.Selected).FirstOrDefault().Text;
return text;
}

Related

How can I convert anonymous object HTML attributes to a Dictionary<string, object>

I'm providing an extra overload to RadioButtonFor and want to add a Key Value pair to the HTML Attributes that are passed in.
As an example I am passing in something like:
new { id = "someID" }
When i the use the HtmlHelper.AnonymousObjectToHtmlAttributes method as seems to be the suggestions I'm finding), its resulting in a dictionary with 4 items with Keys of "Comparer", "Count", "Keys", "Values". I then try to use Reflection to iterate over the values in both "Keys" and "Values", but cannot get that to work either.
Essentially all I want to do is to be able to cast the htmlAttributes to an IDictionary , add a item and then pass it on to a regular RadioButtonFor method.
Edit:
Heres what Im actually trying to do. Provide an overload called isDisabled to be able to set the disabled state of the radio button as this cant be easily done directly using HTML attributes because disabled = false stillr esults in disabled being rendered to tag and disables the radio.
public static MvcHtmlString RadioButtonFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object value, bool isDisabled, object htmlAttributes)
{
var linkAttributes = System.Web.Mvc.HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
Dictionary<string, object> htmlAttributesDictionary = new Dictionary<string, object>();
foreach (var a in linkAttributes)
{
if (a.Key.ToLower() != "disabled")
{
htmlAttributesDictionary.Add(a.Key, a.Value);
}
}
if (isDisabled)
{
htmlAttributesDictionary.Add("disabled", "disabled");
}
return InputExtensions.RadioButtonFor<TModel, TProperty>(htmlHelper, expression, value, htmlAttributesDictionary);
}
Looks like you might be applying the AnonymousObjectToHtmlAttributes either twice or to the wrong item.
Without more of your code, it's hard to tell
var attributes = HtmlHelper.AnonymousObjectToHtmlAttributes(new { id = "someID" });
attributes.Count = 1
attributes.Keys.First() = id
compared with
var attributes = HtmlHelper.AnonymousObjectToHtmlAttributes(HtmlHelper.AnonymousObjectToHtmlAttributes(new { id = "someID" }));
attributes.Count = 3
attributes.Keys.Join = Count,Keys,Values
When writing your overload, make sure your parameter is: object htmlAttributes for the new { } part with an overload with the IDictionary, eg:
Public static MvcHtmlString MyRadioButtonFor(..., object htmlAttributes)
{
return MyRadioButtonFor(...., HtmlHelper.AnonymousObjectToHtmlAttrbites(htmlAttributes);
}
public static MvcHtmlString MyRadioButtonFor(..., IDictionary<string, object> htmlAttributes)
{
htmlAttributes.Add("item", item);
return RadioButtonFor(..., htmlAttributes);
}
(just to be clear, never use My... - it's just for illustration)
Its unclear why you would not just use and existing overload that accepts object htmlAttributes to add the disabled="disabled" attribute, however the following should work
public static MvcHtmlString RadioButtonFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object value, bool isDisabled, object htmlAttributes)
{
IDictionary<string, object> attributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
if (isDisabled && !attributes.ContainsKey("disabled"))
{
attributes.Add("disabled", "disabled");
}
return InputExtensions.RadioButtonFor<TModel, TProperty>(htmlHelper, expression, value, attributes);
}

Get [Display] attribute value from resource in MVC

I have used the [Display] attribute for one of my enums:
public enum eCommentType
{
[Display(ResourceType = typeof(FaultManagementStrings), Name = "NormalComment")]
NormalComment = 0,
[Display(ResourceType = typeof(FaultManagementStrings), Name = "OpenningComment")]
OpenningComment = 1,
[Display(ResourceType = typeof(FaultManagementStrings), Name = "StartProgressComment")]
StartProgressComment = 2,
[Display(ResourceType = typeof(FaultManagementStrings), Name = "ClouserComment")]
ClouserComment = 3,
[Display(ResourceType = typeof(FaultManagementStrings), Name = "ReopennignComment")]
ReopennignComment = 4
}
Is it possible to create an Extention method that will reuse the exsisting MVC finctionallity of getting the Display attribute value from the specified resource ?
I would what something like that...
#Html.GetEnumDisplayAttributeValue(c=> comment.CommentType)
I know i could wirte something that will implement the required reflection and find the value of the resource type and the call resource manager and so on... but i think that maybe it is possible to use the exsisting built in functionally of mvc.. after all it is already done when you call a LabelFor helper.
is is possible or should i reinvent the wheel ?
I have had the same problem and created these extension methods:
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Linq.Expressions;
using System.Web.Mvc;
using System.Web.Mvc.Html;
public static class EnumHelper
{
private static readonly SelectListItem[] SingleEmptyItem = new[] { new SelectListItem { Text = string.Empty, Value = string.Empty } };
public static MvcHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression)
{
return htmlHelper.EnumDropDownListFor(expression, null);
}
public static MvcHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression, object htmlAttributes)
{
var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var enumType = GetNonNullableModelType(metadata);
var values = Enum.GetValues(enumType).Cast<TEnum>();
var items =
values.Select(value => new SelectListItem
{
Text = GetName(value),
Value = value.ToString(),
Selected = value.Equals(metadata.Model)
});
if (metadata.IsNullableValueType)
{
items = SingleEmptyItem.Concat(items);
}
return htmlHelper.DropDownListFor(expression, items, htmlAttributes);
}
private static string GetName<TEnum>(TEnum value)
{
var displayAttribute = GetDisplayAttribute(value);
return displayAttribute == null ? value.ToString() : displayAttribute.GetName();
}
private static DisplayAttribute GetDisplayAttribute<TEnum>(TEnum value)
{
return value.GetType()
.GetField(value.ToString())
.GetCustomAttributes(typeof(DisplayAttribute), false)
.Cast<DisplayAttribute>()
.FirstOrDefault();
}
private static Type GetNonNullableModelType(ModelMetadata modelMetadata)
{
var realModelType = modelMetadata.ModelType;
var underlyingType = Nullable.GetUnderlyingType(realModelType);
return underlyingType ?? realModelType;
}
}
You can use these extension methods in your views as follows:
#Html.EnumDropDownListFor(c=> comment.CommentType)
You should now see a dropdownlist containing the enum values' names according to their DisplayAttribute.
The actual retrieval of the displayed enum value is done in the GetName method, which first uses the GetDisplayAttribute method to try to retrieve the DisplayAttribute of the enum value. If the enum value was indeed decorated with a DisplayAttribute, it will use that to retrieve the name to be displayed. If there is no DisplayAttribute, we just return the name of the enum value.
As was to be expected, Microsoft included an EnumDropDownListFor HTML Helper method in MVC 5.1 (RC1).

Custom Attributes for SelectlistItem in MVC

I would like to create a custom htmlhelper(Extension Method) for dropdownlist to accept custom attributes in the Option tag of the selectlistitem.
I have a property in my model class, that I would like to include as an attribute in the option tag of the selectlist.
i.e <option value ="" modelproperty =""></option>
I have come across various examples but non quite specific to what I would want.
Try this:
public static MvcHtmlString CustomDropdown<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> listOfValues,
string classPropName)
{
var model = htmlHelper.ViewData.Model;
var metaData = ModelMetadata
.FromLambdaExpression(expression, htmlHelper.ViewData);
var tb = new TagBuilder("select");
if (listOfValues != null)
{
tb.MergeAttribute("id", metaData.PropertyName);
var prop = model
.GetType()
.GetProperties()
.FirstOrDefault(x => x.Name == classPropName);
foreach (var item in listOfValues)
{
var option = new TagBuilder("option");
option.MergeAttribute("value", item.Value);
option.InnerHtml = item.Text;
if (prop != null)
{
// if the prop's value cannot be converted to string
// then this will throw a run-time exception
// so you better handle this, put inside a try-catch
option.MergeAttribute(classPropName,
(string)prop.GetValue(model));
}
tb.InnerHtml += option.ToString();
}
}
return MvcHtmlString.Create(tb.ToString());
}
Yeah you can create it by your own.
Create one Extension method which will accept a list of Object which contains all required properties of it. Use TagBuilder to create Tags and use MergeAttribute method of it to add your own attribute to it.
Cheers

How to create an MVC helper method to populate a drop down from an enum

The MVC HtmlHelper.DropDownFor method can be downright frustrating to use. More often than not your selection does not remain or your control is not bound correctly. How would you write a custom HTML helper to populate a dropdown from an enum?
I spent the last few hours trying to figure this one out, so might as well share what I found. After trying all sorts of permutations, creating a test app to try out various options and searching many articles, I got something that works for me.
First point to mention. The SelectList class takes four parameters (last three are optional). If you don't specify the selected value (last param), it will clear out any selected values you had set in your SelectListItem objects (assuming you created a list of those). This frustrated me for a while because I was setting one of the items Selected property to true, but once I create the SelectList object it was always set to false.
Here's the MVC source for SelectList for reference:
public class SelectList : MultiSelectList
{
public SelectList(IEnumerable items)
: this(items, null /* selectedValue */)
{
}
public SelectList(IEnumerable items, object selectedValue)
: this(items, null /* dataValuefield */, null /* dataTextField */, selectedValue)
{
}
public SelectList(IEnumerable items, string dataValueField, string dataTextField)
: this(items, dataValueField, dataTextField, null /* selectedValue */)
{
}
public SelectList(IEnumerable items, string dataValueField, string dataTextField, object selectedValue)
: base(items, dataValueField, dataTextField, ToEnumerable(selectedValue))
{
SelectedValue = selectedValue;
}
public object SelectedValue { get; private set; }
private static IEnumerable ToEnumerable(object selectedValue)
{
return (selectedValue != null) ? new object[] { selectedValue } : null;
}
}
Once I got past that little point I got my helper to correctly select the item from the list and correctly bind the value back. So here's the helper method I created (The initial method was from another post, but that did nor work correctly for me):
public static MvcHtmlString EnumDropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes = null) where TProperty : struct, IConvertible
{
if (!typeof(TProperty).IsEnum)
throw new ArgumentException("TProperty must be an enumerated type");
var selectedValue = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData).Model.ToString();
var selectList = new SelectList(from value in EnumHelper.GetValues<TProperty>()
select new SelectListItem
{
Text = value.ToDescriptionString(),
Value = value.ToString()
}, "Value", "Text", selectedValue);
return htmlHelper.DropDownListFor(expression, selectList, htmlAttributes);
}
(The EnumHelper.GetValues and ToDescriptionString are my helper methods to return a list of enum values of a specified type and to get the EnumMember value property for the description for the enum) I can post that code if anyone wants it.
The trick in that above code was telling SelectList what the value and text properties are as well as the selected value.

Selected Property of SelectListItem and selectedValue parameter of SelectItem not working

I am trying to develop an HtmlHelper extension method: EnumDropDownListFor. No matter what I did I was unable to show the selected value. I tried setting Selected=true property of SelectListItem and setting selectedValue of SelectList constructor. While debugging (at return line) I can see Selected=true for the SelectLİstItem which is supposed to be Selected, for both cases. But when I "View Source" none of the options have selected="selected" attribute.
Where am I going wrong?
Note: Toolkit is my utility class and ToByte is an extension method for Enum
public static MvcHtmlString EnumDropDownListFor<TModel, TProperty>(
this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, string optionLabel = null,
object htmlAttributes = null) where TModel : class
{
var selectedValue = helper.ViewData.Model == null
? default(TProperty)
: expression.Compile()(helper.ViewData.Model);
var enumVals = Toolkit.GetEnumValues(typeof(TProperty));
//var selectList = from enumVal in enumVals.OfType<Enum>()
// select new SelectListItem
// {
// Text = enumVal.GetName(),
// Value = enumVal.ToByte().ToString(),
// Selected = Equals(enumVal, Toolkit.To<Enum>(selectedValue))
// };
// helper.ViewData[(expression.Body as MemberExpression).Member.Name] = Toolkit.To<Enum>(selectedValue).ToByte().ToString();
var selectList = new SelectList(from enumVal in enumVals.OfType<Enum>()
select new
{
TextField = enumVal.GetName(),
ValueField = enumVal.ToByte().ToString()
}, "ValueField", "TextField", Toolkit.To<Enum>(selectedValue).ToByte().ToString());
return helper.DropDownListFor(expression, selectList, optionLabel, htmlAttributes);
}
I solved it (:
That was beacuse I am calling "helper.DropDownListFor" with same expression which returns an Enum type and I was trying to set values of options by "Byte" casted values. So it seems that Expression's return value overrides the given selected value, makes a lot of sense.

Resources