Problem with DropDownListFor SelectedItem - asp.net-mvc

This has totally puzzled me.
Here's my View:
#Html.DropDownListFor(model => model.ScoreDescription,
Model.RatingOptions,
"--",
new { #id = clientId })
And the model:
public decimal? Score { get; set; }
public SelectList RatingOptions
{
get
{
var options = new List<SelectListItem>();
for (var i = 1; i <= 5; i++)
{
options.Add(new SelectListItem
{
Selected = Score.HasValue && Score.Value == Convert.ToDecimal(i),
Text = ((decimal)i).ToRatingDescription(ScoreFactorType),
Value = i.ToString()
});
}
var selectList = new SelectList(options, "Value", "Text");
// At this point, "options" has an item with "Selected" to true.
// Also, the underlying "MultiSelectList" also has it.
// Yet selectList.SelectedValue is null. WTF?
return selectList;
}
}
As the comments suggest, i can't get the selected value to happen.
Is it something to do with the fact i'm using a nullable decimal ? After that loop, options is correct in that it has exactly 1 item with select to true, so it seems i'm doing the right thing.
Now, if i use a different SelectList overload:
var selectedValue = Score.HasValue ? Score.Value.ToString("0") : string.Empty;
var selectList = new SelectList(options, "Value", "Text", selectedValue);
It works. Why? At first i thought it might be a LINQ-trick (e.g deferred execution), but i tried forcing a .ToList() and there is no difference.
It's like setting the Selected property as you create the SelectListItem has no effect, and you have you set it at the end using the SelectList ctor parameter.
Can anyone shed some light on this?

If you look at the implementation of the SelectList class it never actually uses the fact that you are passing a SelectListItem. It works with an IEnumerable. So the Selected property of a SelectListItem is not used. Personally I prefer setting the selected value of a dropdown by setting the value of the corresponding property that you are binding the ddl to.
Example:
public int? Score { get; set; }
public SelectList RatingOptions
{
get
{
var options = Enumerable.Range(1, 5).Select(i => new SelectListItem
{
Text = ((decimal)i).ToRatingDescription(ScoreFactorType),
Value = ((decimal)i).ToString()
});
return new SelectList(options, "Value", "Text");
}
}
and then in the controller action simply set the Score property to the necessary value and in the view use this Score property to bind to:
#Html.DropDownListFor(
model => model.Score,
Model.RatingOptions,
"--",
new { #id = clientId }
)

Related

Dropdownlistfor selected text and value ASP.NET MVC

I have three dropdownlistfor in edit mode, i am able to populate the correct text in DDLFor but the id of it is coming up as 0 on submit, but if i select a different team it is coming up with correct id. Following is my code
ViewModel
public List<SelectListItem> TeamOneList { get; set; }
public string SelectedTeamOne { get; set; }
.... //remaining properties for DDL's..........
Controller
List<SelectListItem> TeamOneList = new List<SelectListItem>();
foreach (var item in db.Teams)
{
TeamOneList.Add(new SelectListItem { Text = item.TeamName, Value = item.TeamId.ToString() });
}
string SelectedTeamOne = db.Teams.Where(o => o.TeamId == fixture.TeamOneId).Select(s => s.TeamName).Single();
View
#Html.DropDownListFor(model => model.TeamOneId, Model.TeamOneList, Model.SelectedTeamOne, new { #class = "form-control" })
Your generating the option value attribute based on the TeamId property of Team, but your setting the SelectedTeamOne value based on the TeamName name property, so the value of SelectedTeamOne does not match any of the options, therefore the first option (the null label option) is selected (because something has to be).
But you generating the null label option with the same text as SelectedTeamOne so it appears your are selecting it when in fact your only selecting the option with a null value (there is actually a second option with the same name in your dropdownlist).
Your need to change the code in the view to
#Html.DropDownListFor(model => model.SelectedTeamOne, Model.TeamOneList, "-Please select-", new { #class = "form-control" })
and then change the controller code to
SelectedTeamOne = db.Teams.Where(o => o.TeamId == fixture.TeamOneId).Select(s => s.TeamId).Single();
Note that it appears TeamId is typeof int, therefore your SelectedTeamOne property should also be int, not string
public int SelectedTeamOne { get; set; }
In addition, you can simply use
var model = new yourModelName
{
TeamOneList = db.Teams.Select(x => new SelectListItem>
{
Value = x.TeamId.ToString(),
Text = x.TeamName
}),
SelectedTeamOne = fixture.TeamOneId // no need to make a database call
};
return View(model);
or, even simpler
var model = new yourModelName
{
TeamOneList = new Selectist(db.Teams, "TeamId", "TeamName"),
SelectedTeamOne = fixture.TeamOneId // no need to make a database call
};
return View(model);

SelectList how to set selected item only when valid value for System.Web.Mvc.SelectList

When you create a SelectList you can optionally pass in the SelectedValue property for which the documentation says
// selectedValue:
// The selected value. Used to match the Selected property of the corresponding
// System.Web.Mvc.SelectListItem.
However, if you pass it a value object which is not contained in the list of items, it still sets the selected value. Try this:
using System.Web.Mvc;
class SomeItem
{
public int id { get; set; }
public string text { get; set; }
}
class CreateSelectList
{
public static SelectList CreateSelectList()
{
List<SomeItem> items = new List<SomeItem>();
for (int i = 0; i < 3; i++)
{
items.Add(new SomeItem() { id = i, text = i.ToString() });
}
// 5 is not in the list of items yet the property SelectedValue does = 5
return new SelectList(items, "id", "text", 5);
}
}
My questions are:
Since I want to lazily set my selected value only if it exists, I just want to pass in a value and have it ignored when it does not exist in the list, but how? (is this a bug or a design feature), or
If you create a SelectList without the SelectedValue, after you have constructed it, how can you set the SelectedValue (again when it exists in the list) ?
If your code is near to your real scenario, you could use something like this
// check if there is any element with id = 5
if (items.Any(i => i.id == 5))
{
// there is an element with id = 5 so I set the selected value
return new SelectList(items, "id", "text", 5);
}
else
{
// there is no element with id = 5 so I don't set the selected value
return new SelectList(items, "id", "text");
}

Can I send a SelectList through the UIHint Control Parameters?

Can I send a SelectList through a Data Annotation? Something like...
[UIHint("DropDownList", "", new SelectList(new[] {"one","two","three"}))]
public virtual int? OptionID { get; set; }
I don't understand the syntax but this seems possible. If so, how do I access it from an editor template?
If not, how could I dynamically send a SelectList to a DropDownList Editor Template? I specifically would like to avoid making a separate template for every SelectList - I have too many of them. Thanks
EDIT: I'm working on the second option (Reflection) because I thought it might be more direct than overriding that 15-syllable monster, the DataAnnotationsModelMetadataProvider.
You might want to take a look at this blog post:
http://mikevdm.com/BlogEntry/Key/Using-UIHint-With-ControlParameters-in-MVC
Here's a relevant quote:
ControlParameters are limited in that they can only accept (compile-time) constant values, in a somewhat unusual syntax, but they do allow simple vales (true/false, enumerated values, etc.), so your templated helper can behave slightly differently based on parameters. This allows you to combine related functionality, usually found in individual templates (with lots of code duplication), into one single template.
In my app, all my drop down lists were either nullable bools (Yes, No, not set) or enums. I took the route of making a separate template for each one, but using helper methods to make the code within each template very minimal.
For example, I have a template called Level (where Level is an enum):
The code is just a couple usings plus....
#Html.DropDownListFor(model => model, Model.ToSelectList<Level>())
I use these extension methods for ToSelectList:
public static SelectList ToSelectList<TEnum>(this TEnum? value) where TEnum : struct
{
var items = GetSelectListItems<TEnum>().ToList();
items.Insert(0, new SelectListItem { Value = "", Text = LabelIfNull });
return new SelectList(items, "Value", "Text", value.ToString());
}
public static SelectList ToSelectList<TEnum>(this TEnum value)
{
var items = GetSelectListItems<TEnum>();
return new SelectList(items, "Value", "Text", value.ToString());
}
public static IEnumerable<SelectListItem> GetSelectListItems<TEnum>()
{
var values = System.Enum.GetNames(typeof(TEnum));
return values.Select(v => new SelectListItem { Value = v, Text = v.ToFriendlyName() });
}
public static SelectList ToSelectList(this bool isTrue)
{
var items = new[]
{
new SelectListItem { Value = "true", Text = LabelIfTrue },
new SelectListItem { Value = "false", Text = LabelIfFalse }
};
return new SelectList(items, "Value", "Text", isTrue.ToString());
}
public static SelectList ToSelectList(this bool? isTrue)
{
var items = new[]
{
new SelectListItem { Value = string.Empty, Text = LabelIfNull },
new SelectListItem { Value = "true", Text = LabelIfTrue },
new SelectListItem { Value = "false", Text = LabelIfFalse }
};
return new SelectList(items, "Value", "Text", !isTrue.HasValue ? string.Empty : isTrue.Value.ToString());
}
Perhaps you could use control parameters to get down to a single template, but you'll definitely need to write your own DataAnnotationsModelMetadataProvider, as indicated in the blog post.

Creating a SelectListItem with the disabled="disabled" attribute

I'm not seeing a way to create, via the HtmlHelper, a SelectListItem that will spit out the following HTML:
<option disabled="disabled">don't click this</option>
The only properties SelectListItem has are:
new SelectListItem{
Name = "don't click this",
Value = string.Empty,
Selected = false
}
The only option I see is to
Subclass the SelectListItem to add an Enabled property to get the value to the view
Not use the HTML helper for DropDownList
Create a new HtmlHelper extension that accepts my new EnablableSelectList and adds my disabled attribute.
The Disabled property is supported since ASP.NET MVC 5.2:
new SelectListItem {
// ...
Disabled = true
}
See the API reference.
This is something I might try before recreating the helper completely. The basic idea is that the Html you get from the helper should be well formed, so it should be safe to parse. So you can build on that idea by making your own extension that uses the existing extension but adds the functionality to disable the items.
Something like this might do (totally untested)
public class CustomSelectItem : SelectListItem
{
public bool Enabled { get; set; }
}
public static class CustomHtmlHelpers
{
public static MvcHtmlString MyDropDownList(this HtmlHelper html, IEnumerable<CustomSelectItem> selectList)
{
var selectDoc = XDocument.Parse(html.DropDownList("", (IEnumerable<SelectListItem>)selectList).ToString());
var options = from XElement el in selectDoc.Element("select").Descendants()
select el;
foreach (var item in options)
{
var itemValue = item.Attribute("value");
if (!selectList.Where(x => x.Value == itemValue.Value).Single().Enabled)
item.SetAttributeValue("disabled", "disabled");
}
// rebuild the control, resetting the options with the ones you modified
selectDoc.Root.ReplaceNodes(options.ToArray());
return MvcHtmlString.Create(selectDoc.ToString());
}
}
Clientside option: if you for example give your dropdownlist a class 'custom' and the items that should be unselectable the value -1 (for example), then you can do something like:
$('select.custom option[value=-1]').each(function () {
$(this).attr("disabled", "disabled");
});
If all you are trying to do is prevent a user from selecting a certain value from the list, it seems like the simpler and more time-efficient way to do it is to use input validation. Which you may quite possibly be doing anyways, if you want to verify they've made a selection to begin with.
-----Option 1
Controller:
var ExpectedShipmentsRange = new List();
ExpectedShipmentsRange.Add(new SelectListItem() { Text = "Selected number of shipments", Value="0", Disabled = true, Selected = true });
ExpectedShipmentsRange.Add(new SelectListItem() { Text = "0 to 20 shipments", Value = "0-20" });
ExpectedShipmentsRange.Add(new SelectListItem() { Text = "20 to 40 shipments", Value = "20-40" });
ViewBag.ExpectedShipmentsRange = ExpectedShipmentsRange;
View:
#Html.DropDownListFor(m => m.ExpectedShipments, (IEnumerable<SelectListItem>)#ViewBag.ExpectedShipmentsRange, new { #class = "form-control" })
-----Option 2
Controller:
ViewBag.citiesSa = _dbContext.Countries.ToList();
View:
#Html.DropDownListFor(m => m.City, new SelectList(#ViewBag.citiesSa, "Id", "Name"), "Select your city", new { #class = "form-control" })
-----Option 3 does not support disabled option:
List<SelectListItem> ExpectedShipmentsRange = new List<SelectListItem>();
ExpectedShipmentsRange.Add(new SelectListItem() { Text = "0 to 20 shipments", Value = "0-20" });
ExpectedShipmentsRange.Add(new SelectListItem() { Text = "20 to 40 shipments", Value = "20-40" });
ViewBag.ExpectedShipmentsRange = new SelectList(ExpectedShipmentsRange, "Value", "Text");
View:
#Html.DropDownListFor(m => m.ExpectedShipments, (SelectList)#ViewBag.ExpectedShipmentsRange, new { #class = "form-control" })
I've noticed that while using SelectList to populate the DropDownListFor() method the Disabled & Selected parameters are not respected. They are only honored when populating using List<SelectListItem>. However I've run into other odd bugs when populating the DropDownListFor() using List<SelectListItem> and found that using SelectList is the correct option for populating a DropDownListFor() list. Here's an example of how to create a SelectList list:
public static SelectList States = new SelectList(new[]
{
new SelectListItem { Text = "AL", Value = "AL" },
new SelectListItem { Text = "AK", Value = "AK" },
...
}, "Value", "Text", 1);
In my case I needed to disable the first item of the select list, the only way I was able to do so using the SelectList was by creating an extension method for the DropDownListFor() method. Here is the class I used:
public static class HtmlHelperExtensions
{
private static MvcHtmlString DisableFirstItemDropDownListFor(MvcHtmlString source, string sourceItemName, string sourceItemValue = "", string targetItemValue = "")
{
string htmlString = source.ToHtmlString();
if (string.IsNullOrEmpty(sourceItemValue))
{
htmlString = htmlString.Replace("value=\"\"", "value=\"\" disabled=\"disabled\" selected=\"selected\"");
}
return new MvcHtmlString(htmlString);
}
public static MvcHtmlString DisableFirstItemDropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList, object htmlAttributes)
{
return DisableFirstItemDropDownListFor(htmlHelper.DropDownListFor(expression, selectList, htmlAttributes), string.Empty);
}
}
You can then use this method in your .cshtml file like so:
#Html.DisableFirstItemDropDownListFor(x => x.YourFieldType, Model.YourModel, new { #class = "YourClass" })

Adding a default SelectListItem

public IEnumerable<SelectListItem> GetList(int? ID)
{
return from s in db.List
orderby s.Descript
select new SelectListItem
{
Text = s.Descript,
Value = s.ID.ToString(),
Selected = (s.ID == ID)
};
}
I return the above to a view and populate a DropDownList. I would like to add a default SelectListItem (0, "Please Select..") to the above linq result before it is returned to the view. Is this possible?
return new[] { new SelectListItem { Text = ... } }.Concat(
from s in db.List
orderby s.Descript
select new SelectListItem
{
Text = s.Descript,
Value = s.ID.ToString(),
Selected = (s.ID == ID)
});
As you are using ASP.NET MVC, you can do this in the view by specifying a value for the optionLabel parameter of the DropDownField method of the HtmlHelper - e.g:
htmlHelper.DropDownList("customerId", selectList, "Select One");
Putting this type of code in your UI layer is probably more appropriate than having it in the data layer. One downside to doing this is that your select box will have an empty string value, not a "0" for the 'Select One' option, but that is not really a problem as you can treat this as a null value if your controller action method can accept a nullable int for the relevant parameter - e.g.
public ActionResult DoSomething(int? customerId)
{
if(customerId != null)
{
// do something with the value
}
}
var list = from s in db.List
orderby s.Descript
select new SelectListItem
{
Text = s.Descript,
Value = s.ID.ToString(),
Selected = (s.ID == ID)
};
list.Insert(0, new SelectListItem { Text = "Please Select...", Value = string.Empty });
return list;
firt put your default value in the list
list.add(your default list item)
and then do list.addrange(linq select query)
cheers
Here is what I did, I read my values from an XML file into an IList. I then inserted a new record into the IList at position 0. Then make a select list from the IList.
IList< MY_DATA > mydata = (from tmp in myXML.Descendants("R").ToList()
select new MY_DATA
{
NR = tmp.Attribute("NR").Value,
NA = tmp.Attribute("NA").Value
}).ToList<MY_DATA>();
mydata.Insert(0, new My_DATA() { NR = "", NA = "--Click to Select--" });
SelectList mylist = new SelectList(mydata, "NR", "NA");
After I reviewed many questions, and I don't find what i'm looking for.I don't need many lines of code to just have a simple drowpdown. so I want to share with you what I use and it easy and simple.. ( specially if you don't want to use and Entity Framework..
using System.Web.Mvc;
SelectList(IEnumerable items, string dataValueField, string dataTextField);
For Example:
in Controller add :
SelectList slTitle = new SelectList(Query.SelectAllTitle(), "TitleID", "TitleName");
ViewBag.TitleSelectList = slTitle;
in View add :
#Html.DropDownList("TitleSelectList", "--please select--")
This is just for people who are using Webpages framwork (not MVC and webform) with Razor V2 and C#, you need do
#Html.DropDownList("category", "Please select", listData)
Note: OptionalLabel need to be the middle parameter.
Equivalent would be add a SelectListitem to your list data:
var list=db.Query("select id, name from dbtable");
List<SelectListItem> listData = new List<SelectListItem>();
listData.Add(new SelectListItem
{
Text = "Please select",
Value = "",
});
foreach(var item in list) {
listData.Add(new SelectListItem {
Text = item.Name,
Value = item.id,
Selected = isSelected
});
}
#Html.DropDownList("play" , listData)

Resources