ASP.NET MVC MultiSelectList with selected values not selecting properly - asp.net-mvc

I know others have asked this question, but I'm totally confused by this:
This displays the dropdown with no values selected:
<%= Html.DropDownList("items", new MultiSelectList(Model.AvailableItems,
"id", "name", Model.items), new { multiple = "multiple" })%>
This displays the dropdown with the values that I'm passing in (Model.items) selected properly like what I'd expect:
<%= Html.DropDownList("somethingelse", new MultiSelectList(Model.AvailableItems,
"id", "name", Model.items), new { multiple = "multiple" })%>
But the problem is that this item is now named "somethingelse" when i POST. I know I can hack around this but what's going?

Too little context provided in your question but I will try to show a full working example:
Model:
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
}
public class MyModel
{
public IEnumerable<int> SelectedItemIds { get; set; }
public IEnumerable<Item> AvailableItems {
get
{
return new[]
{
new Item { Id = 1, Name = "Item 1" },
new Item { Id = 2, Name = "Item 2" },
new Item { Id = 3, Name = "Item 3" },
};
}
}
}
Controller:
[HandleError]
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new MyModel
{
SelectedItemIds = new[] { 2, 3 }
};
return View(model);
}
[HttpPost]
public ActionResult Index(IEnumerable<int> selectedItemIds)
{
var model = new MyModel
{
// Important: Don't ever try to modify the selectedItemIds here
// The Html helper will completely ignore it and use
// the POSTed values
SelectedItemIds = selectedItemIds
};
return View(model);
}
}
View:
<% using (Html.BeginForm()) { %>
<%= Html.ListBoxFor(x => x.SelectedItemIds,
new MultiSelectList(Model.AvailableItems, "Id", "Name")) %>
<input type="submit" value="GO" />
<% } %>
Notice that the Html.ListBoxFor is more adapted if you want to generate a multiple select. Obviously the AvailableItems property should be fetched from a repository.

The problem you have is using Model.Items as a parameter. The code
<%= Html.DropDownList("items", new MultiSelectList(Model.AvailableItems,
"id", "name", Model.items), new { multiple = "multiple" })%>
isn't actually working as you would expect. It's working because the name of the dropdown is "items". That's because there was a form param called "items" posted back to your action. That param gets stored in the action's ViewState (don't confuse with ViewData).
The Html.DropdownList() sees that there is a ViewState param named the same as you have named your dropdown and uses that ViewState param to work out the selected values. It completely ignores the Model.items that you passed in.
If anyone can explain the logic of not being able to override the default behavior then I'd love to hear it.
So, that's your first problem. To get around it all you have to do is to rename the dropdown to something else - exactly like you did in your second example. Now your second problem comes into play: the list of selected items must be a collection of simple objects (I think it actually needs to be an IEnumerable but I'm not 100% sure).
The DropDownList() method will try and match those selected values to the Value in your AvailableItems collection. If it can't do that it will try to match against the Text.
So, try this to see if it works
<%= Html.DropDownList("somethingelse", new MultiSelectList(Model.AvailableItems,
"id", "name", Model.items.Select(c=> c.name)), new { multiple = "multiple" })%>
Good luck

Actually, if you look at the MVC source code this behavior is baked into DropDownListFor by default (search for allowMultiple: false). The solution is to use ListBoxFor instead (you will see that as well in the MVC source code, allowMultiple: true), which makes a lot of sense as HTML wise, both render to
<select ...>
<option ...>
<option ...>
...
</select>
You don't have to use different properties on the model as suggested in the answers above this one, I got this working by simply switching to ListBoxFor instead (CSS takes it from there):
#Html.ListBoxFor(model => model.SelectedCategories,
new MultiSelectList(Model.Categories, Model.SelectedCategories),
new { multiple = "multiple" })
Works like a charm, even with POST and re-displaying the view on error.

I had the same problem, I used my own extention method to generate the html and problem solved
public static MvcHtmlString ListBoxMultiSelectFor<TModel, TProperty>(
this HtmlHelper<TModel> helper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> selectList,
object htmlAttributes)
{
return ListBoxMultiSelectFor(helper, expression, selectList, new RouteValueDictionary(htmlAttributes));
}
public static MvcHtmlString ListBoxMultiSelectFor<TModel, TProperty>(
this HtmlHelper<TModel> helper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> selectList,
IDictionary<string, object> htmlAttributes)
{
string name = ExpressionHelper.GetExpressionText(expression);
TagBuilder selectTag = new TagBuilder("select");
selectTag.MergeAttributes(htmlAttributes);
selectTag.MergeAttribute("id", name, true);
selectTag.MergeAttribute("name", name, true);
foreach (SelectListItem item in selectList)
{
TagBuilder optionTag = new TagBuilder("option");
optionTag.MergeAttribute("value", item.Value);
if (item.Selected) optionTag.MergeAttribute("selected", "selected");
optionTag.InnerHtml = item.Text;
selectTag.InnerHtml += optionTag.ToString();
}
return new MvcHtmlString(selectTag.ToString());
}

I had a similar problem, when using ListBoxFor and a MultiSelectList assigned as a ViewBag property. #jero's answer helped me figure out that if there's a naming collision between the ViewBag field and the model field, then the selected values don't appear properly.
If I did the following, the items did not show up as selected.
//Controller
ViewBag.SelectionIds = new MultiSelectView(possibleValues, "Value", "Name", selectedValues);
//View
#Html.ListBoxFor(model => model.SelectionIds, (MultiSelectList)ViewBag.SelectionIds, new { #class = "form-control" })
If I changed it to the following, then it was fine.
//Controller
//Changed ViewBag.SelectionIds to ViewBag.SelectionIdList
ViewBag.SelectionIdList = new MultiSelectView(possibleValues, "Value", "Name", selectedValues);
//View
#Html.ListBoxFor(model => model.SelectionIds, (MultiSelectList)ViewBag.SelectionIdList, new { #class = "form-control" })

You can go to the to the value of "items" with this
<HttpPost()> _
Function Edit(ByVal crm_cliente As crm_cliente, ByVal form As FormCollection) As ActionResult
If ModelState.IsValid Then
Dim items As String
crm_cliente.usuario_modifico = "ejmorales"
crm_cliente.fecha_modifico = Date.Now
items = form("items")
that will get you the selected items as a string separate with commas (,)

Related

How to work with DropDownListFor in an EDIT view

Hi I have a problem with DropDownListFor on the Edit view.
Basically I'm using a partial view which contains my form and in my Edit and Create view I call this partial view.
I have around 5 similiar DropdownlistFor and these work well on create action but in edit doesn't, mainly i'm not getting (unable) to set the selected value.
In my Edit Action (GET), I fill my property ViewModel if the true object has the property filled.
if(icb.BAOfficer != null)
editICB.BAOfficer = icb.BAOfficer;
List<Staff> staffs = _fireService.GetAllStaffs().ToList();
staffs.Insert(0, new Staff { StaffId = -1, Name = "" });
editICB.BAOfficers = staffs;
return View(editICB);
This is how I'm filling my drop down and how I'm trying to set the selected value.
#Html.DropDownListFor(model => model.BAOfficerSelected, new SelectList(Model.BAOfficers, "StaffId", "Name", (Model.BAOfficer!= null ? Model.BAOfficer.StaffId:-1)), new { #class = "rounded indent" })
#Html.ValidationMessageFor(model => model.BAOfficer.StaffId)
I solve the problem setting a value to my model.BAOfficerSelected in Edit Action, this was the (easy) secret.
I need the first item like a empty option because is not a required information, but on the edit view if has value I need to set it as selected option.
In the end, it was my code.
My Model
public int BAOfficerSelected { get; set; }
public SelectList BAOfficers { get; set; }`
My Controller Create/Edit Action
if (icb.BAOfficer != null) // only for edit action
editICB.BAOfficerSelected = icb.BAOfficer.StaffId; //this will set the selected value like a mapping
//for Edit and Create
List<Staff> staffs = _fireService.GetAllStaffs().ToList();
staffs.Insert(0, new Staff { StaffId = -1, Name = "" });
editICB.BAOfficers = new SelectList(staffs, "StaffId", "Name");
return View(editICB);`
My View
#Html.DropDownListFor(model => model.BAOfficerSelected, Model.BAOfficers, new { #class = "rounded indent" })
I hope this can help others.
The best and cleanest way of doing this is setting the selected value in server side, in the SelectList object.
So, if your BAOfficerSelected is nullable... it is all simpler: You don't need to rely in adding a dummy item to hold the -1 for not selected value.
Instead, you do it this way:
List<Staff> staffs = _fireService.GetAllStaffs().ToList();
editICB.BAOfficers = new SelectList(staffs, "StaffId", "Name", editICB.BAOfficer != null ? editICB.BAOfficer.StaffId : null);
Of course, the BAOfficers need to be changed type from List<Staff> to SelectList.
Then, in your partial view you do:
#Html.DropDownListFor(model => model.BAOfficerSelected, Model.BAOfficers, "Select one...", new { #class = "rounded indent" })
Adding the 3rd parameter is needed to indicate that the default value (if nothing is selected) is that text.
Instead of using a SelectList, I often find it works better to use a List<SelectListItem>.
Further, I usually use an EditorTemplate for my dropdowns to keep my views clean.
So if my select list returns List<SelectListItem>:
public List<SelectListItem> BAOfficers { get; set };
You can set it up like this:
model.BAOfficers = staffs.Select(staff =>
new SelectListItem { Text = staff.Name, Value = staff.StaffId }).ToList();
Then in your EditorTemplate:
<!-- EditorTempaltes\DropDownList.cshtml -->
#model System.String
<p>
#Html.LabelFor(m => m):
#Html.DropDownListFor(m => m, new SelectList(
(List<SelectListItem>)ViewData["selectList"], "Value", "Text",
String.IsNullOrEmpty(Model) ? String.Empty : Model), String.Empty)
#Html.ValidationMessageFor(m => m)
</p>
And then in the view, just pass the SelectList into the EditorTemplate:
#Html.EditorFor(m => m.BAOfficerSelected, "DropDownList",
new { selectList = Model.BAOfficers() })
I met the same problem ,too.
According the article https://dotnetfiddle.net/PIGNLF which way gave a simple way to deal with this problem without two Models or more classes.enter link description here
here is my code
add model
public class NoteSelectLisModel
{
public string Value { get; set; }
public string Name { get; set; }
}
add Controller
public ActionResult Edit(int? _ID)
{
ViewBag.NoteState = new SelectList(new List<NoteSelectLisModel>()
{
new NoteSelectLisModel() {Value="1",Name="A)"},
new NoteSelectLisModel() {Value="2",Name="B"},
new NoteSelectLisModel() {Value ="3",Name ="C"}
}, "Value", "Name", 1);
Table ut = _db.Tables.Find(_ID);
if (ut == null)
{
return HttpNotFound();
}
else
{
return View(ut);
}
}
add View .cshtml
#Html.DropDownListFor(m => m.NOTE, (IEnumerable<SelectListItem>)ViewBag.NoteState, "No Selected")
The edit's Model is the same and the dropdownlist passed by View.bag

SelectList selected value not carried over to DropDownList

I have a Razor page with a drop down list inside a form:
#using (Html.BeginForm("ProductsByOwners", "Report", FormMethod.Post, new { #id = "ProductsByOwners" }))
{
#Html.Label("Choose product owner: ")
#Html.DropDownList("OwnerList", (SelectList)ViewBag.OwnerList, new { #onchange = "this.form.submit();" })
}
The selected value of my SelectList is not being carried over to the DropDownList. I've debugged and stepped through the code and found that (SelectList)ViewBag.OwnerList evaluates properly and has the expected value selected, but the resulting HTML does not have any of the option tags selected.
Can anyone see what I'm doing wrong here?
UPDATED
Here is how the SelectList is created in my action:
ViewBag.OwnerList = new SelectList(ListUtils.ProductOwners(), "Key", "Value", values["OwnerList"]);
The result has the value indicated by values["OwnerList"] selected.
Thanks!
You are not using the DropDownList helper properly. In order to create a dropdownlist you need 2 things:
a scalar property to bind to the selected value when the form is submitted
a collection to bind the options to
In your example you have only one of those 2 things (the second). Your first argument is called OwnerList and you have ViewBag.OwnerList passed as second argument.
So:
#Html.DropDownList(
"SelectedOwnerId",
(SelectList)ViewBag.OwnerList,
new { #onchange = "this.form.submit();" }
)
Obviously I would recommend you using strongly typed views ans view models. And obviously get rid of the weakly typed ViewBag/ViewData/ViewCrap.
So start by designing a view model to meet the requirements of your view (which from what you have shown so far is to display a dropdownlist):
public class OwnerViewModel
{
[DisplayName("Choose product owner: ")]
public string SelectedOwnerId { get; set; }
public IEnumerable<SelectListItem> OwnerList { get; set; }
}
then a controller:
public class ReportController: Controller
{
public ActionResult ProductsByOwners()
{
var model = new OwnerViewModel
{
// preselect the second owner
SelectedOwnerId = "2",
// obviously those come from your database or something
OwnerList = new[]
{
new SelectListItem { Value = "1", Text = "owner 1" },
new SelectListItem { Value = "2", Text = "owner 2" },
new SelectListItem { Value = "3", Text = "owner 3" },
}
};
return View(model);
}
[HttpPost]
public ActionResult ProductsByOwners(OwnerViewModel model)
{
...
}
}
and you have a corresponding strongly typed view:
#model OwnerViewModel
#using (Html.BeginForm("ProductsByOwners", "Report", FormMethod.Post, new { id = "ProductsByOwners" }))
{
#Html.LabelFor(x => x.SelectedOwnerId)
#Html.DropDownListFor(
x => x.SelectedOwnerId,
Model.OwnerList,
new { onchange = "this.form.submit();" }
)
}
The most common reason the selected item is not selected in the DDL is you've named the selectlist the same as the model.
Strongly typed views are preferred, but it's fine to pass the SelectList in a Viewbag. See my tutorial Working with the DropDownList Box and jQuery and my blog Cascading DropDownList in ASP.Net MVC

The ViewData item that has the key 'MY KEY' is of type 'System.String' but must be of type 'IEnumerable<SelectListItem>'

I am trying to populate a dropdown list from a database mapped with Linq-2-SQL, using ASP.NET MVC 2, and keep getting this error.
I am so confused because I am declaring a variable of type IEnumerable<SelectListItem> on the second line, but the error makes me think this is not the case. I feel like this should be very simple, but I am struggling. Any help is appreciated.
Here are the interesting bits of my controller:
public ActionResult Create()
{
var db = new DB();
IEnumerable<SelectListItem> basetypes = db.Basetypes.Select(
b => new SelectListItem { Value = b.basetype, Text = b.basetype });
ViewData["basetype"] = basetypes;
return View();
}
And here are the interesting bits of my view:
<div class="editor-label">
<%: Html.LabelFor(model => model.basetype) %>
</div>
<div class="editor-field">
<%: Html.DropDownList("basetype") %>
<%: Html.ValidationMessageFor(model => model.basetype) %>
</div>
Here is the POST action when submitting the Form
// POST: /Meals/Create
[HttpPost]
public ActionResult Create(Meal meal)
{
if (ModelState.IsValid)
{
try
{
// TODO: Add insert logic here
var db = new DB();
db.Meals.InsertOnSubmit(meal);
db.SubmitChanges();
return RedirectToAction("Index");
}
catch
{
return View(meal);
}
}
else
{
return View(meal);
}
}
Thanks.
I had same problem, and finally I got the answer...
The problem is that in the POST action, after submitting the form, the ModelState is not valid, or it's catching an error in try/catch, so the View is returned. But this time the View has not the ViewData["basetype"] correctly set.
You need to populate it again, probably with the same code used before, so repeat this:
var db = new DB();
IEnumerable<SelectListItem> basetypes = db.Basetypes.Select(
b => new SelectListItem { Value = b.basetype, Text = b.basetype });
ViewData["basetype"] = basetypes;
before the return View(meal) in the [HttpPost] method.
exactly this will solve your problem:
[HttpPost]
public ActionResult Create(Meal meal)
{
if (ModelState.IsValid)
{
try
{
// TODO: Add insert logic here
var db = new DB();
db.Meals.InsertOnSubmit(meal);
db.SubmitChanges();
return RedirectToAction("Index");
}
catch
{
var db = new DB();
IEnumerable<SelectListItem> basetypes = db.Basetypes.Select(
b => new SelectListItem { Value = b.basetype, Text = b.basetype });
ViewData["basetype"] = basetypes;
return View(meal);
}
}
else
{
var db = new DB();
IEnumerable<SelectListItem> basetypes = db.Basetypes.Select(
b => new SelectListItem { Value = b.basetype, Text = b.basetype });
ViewData["basetype"] = basetypes;
return View(meal);
}
}
I know this question is very old, but I came here today with the same problem, so other could come here later...
You will receive this error if the SelectList is null.
I've just come across this issue and this article helped me through it - http://odetocode.com/Blogs/scott/archive/2010/01/18/drop-down-lists-and-asp-net-mvc.aspx
The most likely cause it that your collection is repopulated after the po
For future readers, if you are using razor, try to change type of selectlist item from List to IEnumerable.
From
#Html.DropDownListFor(m => m.id, ViewBag.SomeList as List<SelectListItem>)
To
#Html.DropDownListFor(m => m.id, ViewBag.SomeList as IEnumerable<SelectListItem>)
Try adding a string for the name of your dropdown list as the first parameter, and get the item out of your viewdata:
<%= Html.DropDownList("SomeDropdownName", (IEnumerable<SelectListItem>)ViewData["basetype"]) %>
Here is also an extension method you can use so the dropdown list is set up in a similar style to how you have done your other controls:
public static string DropDownList<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList, string optionLabel)
where TModel : class
{
string inputName = ExpressionHelper.GetInputName(expression);
return htmlHelper.DropDownList(inputName, selectList, optionLabel);
}
For example
<%= Html.DropDownList(x => x.BaseType, (IEnumerable<SelectListItem>)ViewData["basetype"], "")%>
You are setting the collection as an item in ViewData dictionary and trying to retreive it as property on the model. A simple fix would be to reference it the same way as you set it:
<%var basetype = ViewData["basetype"] as IEnumerable<SelectListItem>;%>
<div class="editor-label">
<%: Html.Label("basetype") %>
</div>
<div class="editor-field">
<%: Html.DropDownList("basetype", basetype) %>
<%: Html.ValidationMessage("basetype") %>
</div>
Alternatively, the below code uses a strongly typed view:
public class ViewModel {
//Model properties
public IEnumerable<SelectListItem> basetype {get;set;}
}
public ActionResult Create()
{
var db = new DB();
IEnumerable<SelectListItem> basetypes = db.Basetypes.Select(b => new SelectListItem { Value = b.basetype, Text = b.basetype });
return View(new ViewModel { basetype=basetypes });
}
Then, in your strongly typed view:
<div class="editor-label">
<%: Html.LabelFor(model => model.basetype) %>
</div>
<div class="editor-field">
<%: Html.DropDownListFor(model=>model.basetype) %>
<%: Html.ValidationMessageFor(model => model.basetype) %>
</div>
If you use Html.DropDownList() method - same error may occur, if your ViewData/Viewbag item not set, as #Peto answered.
But it may be not set in case of controller set item correctly, but in main view you use partial viw call with new ViewDataDictionary values.
if you have #Html.Partial("Partianame", Model,new ViewDataDictionary() { /* ... */ }) then your partial view will not see ViewData and ViewBag data, remove new ViewDataDictionary() parameter
To future readers,
I came across with the problem today and couldnĀ“t fix it. It turned to be really simple after all. I was working with a table + view. When I updated the table (added a few colums) I forgot to update (drop and recreate) the view, which caused the problem for me.
Hope it helps somebody.
I got same error today and my solution is to make model "valid".
In my case, after user submit by clicking "save", I got model state: invalid if user key-in "0", but model state will be valid if user key-in "0.0".
So I override "IsValid" method to return true even user key-in "0".
Hope it helps.

Challenges with selecting values in ListBoxFor

Working on my first ASP.Net MVC2 web app recently, I came across some issues when I needed to select multiple values in a list box. I worked around it with some jQuery, but went ahead and put together some very simple code to demonstrate. I'm using EF for the model, with two entities - Customers and HelpDeskCalls:
Controller:
public ActionResult Edit(int id)
{
Customer currCustomer = ctx.Customers.Include("HelpDeskCalls").Where(c => c.ID == id).FirstOrDefault();
List<HelpDeskCall> currCustCalls = (ctx.HelpDeskCalls.Where(h => h.CustomerID == id)).ToList();
List<SelectListItem> currSelectItems = new List<SelectListItem>();
List<String> selectedValues = new List<string>();
foreach (HelpDeskCall currCall in currCustCalls)
{
bool isSelected = (currCall.ID % 2 == 0) ? true : false;
//Just select the IDs which are even numbers...
currSelectItems.Add(new SelectListItem() { Selected = isSelected, Text = currCall.CallTitle, Value = currCall.ID.ToString() });
//add the selected values into a separate list as well...
if (isSelected)
{
selectedValues.Add(currCall.ID.ToString());
}
}
ViewData["currCalls"] = (IEnumerable<SelectListItem>) currSelectItems;
ViewData["currSelected"] = (IEnumerable<String>) selectedValues;
return View("Edit", currCustomer);
}
View:
<div class="editor-field">
<%: Html.ListBoxFor(model => model.HelpDeskCalls, new MultiSelectList(Model.HelpDeskCalls, "ID", "CallTitle", (IEnumerable) ViewData["currSelected"]), new { size = "12" })%>
<%: Html.ListBoxFor(model => model.HelpDeskCalls, ViewData["currCalls"] as IEnumerable<SelectListItem>, new { size = "12"}) %>
<%: Html.ListBox("Model.HelpDeskCalls", new MultiSelectList(Model.HelpDeskCalls, "ID", "CallTitle", (IEnumerable)ViewData["currSelected"]), new { size = "12"}) %>
<%: Html.ValidationMessageFor(model => model.HelpDeskCalls) %>
</div>
For this sample, I'm just selecting HelpDeskCall.IDs which are even. I'm trying two different syntaxes for ListBoxFor: One uses an IEnumerable of values for selections, one using an IEnumerable of SelectListItems. By default, when I run this code, no selections are made to either ListBoxFor, but the non-strongly typed ListBox selects correctly.
I read this post on ASP.Net and this thread on SO, but no joy. In fact, if I add the override ToString() to my HelpDeskCall class (as suggested in the ASP.net thread) all values are selected, which isn't right either.
If someone could shed some light on how this should work (and what I'm missing or doing wrong), this then neophyte would be very grateful.
Here's an example illustrating the strongly typed version:
Model:
public class MyViewModel
{
public int[] SelectedItemIds { get; set; }
public MultiSelectList Items { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
// Preselect items with id 1 and 3
var selectedItemIds = new[] { 1, 3 };
var model = new MyViewModel
{
Items = new MultiSelectList(
new[]
{
// TODO: Fetch from your repository
new { Id = 1, Name = "item 1" },
new { Id = 2, Name = "item 2" },
new { Id = 3, Name = "item 3" },
},
"Id",
"Name",
selectedItemIds
)
};
return View(model);
}
}
View:
<%: Html.ListBoxFor(x => x.SelectedItemIds, Model.Items) %>
I don't know if this behaviour has changed in the RTM of MVC3 that I'm using, but it seems that selection and binding now works out of the box. The only catch is that the model should contain a property with the IDs, like that :
public class MyViewModel {
public int[] ItemIDs { get; set; }
}
Then the following in the view would work fine, both pre-selecting the correct values and binding correctly during post:
#Html.ListBoxFor(model => model.ItemIDs, (IEnumerable<SelectListItem>)(new[] {
new SelectListItem() { Value = "1", Text = "1" },
new SelectListItem() { Value = "2", Text = "2" }
}))
I have found better workaround. Usual way to preselect select list:
#Html.ListBoxFor(
model => model.Roles,
new MultiSelectList(db.Roles, "Id", "Name")
)
#Html.ValidationMessageFor(model => model.Roles)
Doesn't work.., never ever any option is selected, until:
public ActionResult Edit(int id)
{
var user = db.Users.Find(id);
// this is workaround for http://aspnet.codeplex.com/workitem/4932?ProjectName=aspnet
ViewData["Roles"] = user.Roles.Select(r => r.Id);
return View(user);
}
Selected Roles has to be stored in ViewData, to workaround nasty bug.
Another option is to take advantage of nameof, you could do something like this;
Html.ListBox(nameof(MainProjectViewModel.Projects), Model.Projects)

DropDownListFor in EditorTemplate not selecting value

I have an editor template for a custom object. Inside that editor template I use a couple of DropDownListFor helpers. In each of them I specify a unique model property (with the pre-selected value) and the select list containing all the select options.
Example:
<%=Html.DropDownListFor(m => m.DocumentCategoryType, Model.DocumentCategoryTypeList) %>
I know that the option values are being populated (from viewing source) and that my Model is passed in with the correct ID value (DocumentCategoryType).
When the view is rendered, there is no selected item in my dropdown and therefore it defaults to the first (non-selected) value.
Does anyone have any ideas?
Thanks.
We also solved the solution by populating a new SelectList that has the appropriate SelectListItem selected, but created this extension method to keep the call to DropDownListFor a little cleaner:
public static SelectList MakeSelection(this SelectList list, object selection)
{
return new SelectList(list.Items, list.DataValueField, list.DataTextField, selection);
}
Then your DropDownListFor call becomes:
<%= Html.DropDownListFor(m => m.DocumentCategoryType, Model.DocumentCategoryTypeList.MakeSelection(Model.DocumentCategoryType)) %>
Looking through the ASP.NET MVC 2 source code reveals some solutions to this problem. Essentially, any SelectListItem in the SelectList passed in the helper extension method that has the Selected property set to true does not have any bearing over the <option> element rendered with the selected attribute applied for the item.
The selected attribute on <option> elements is determined by
1) checking that the helper extension method was passed a SelectList. If this is null, the framework will look in the ViewData for a value corresponding to the key that is the view model property for which you wish to render the drop down list for. If the value is a SelectList, this will be used to render the <select> including taking any selected values, so long as the model state for the model property is null.
2) If a SelectList has been passed in the helper extension method and the model state for the model property is null, the framework will look in the ViewData for a default value, using the model property name as the key. The value in view data is converted to a string and any items in the SelectList passed to the helper extension method that have a value (if no value is set, then the Text will be checked) that matches the default value will have the Selected property set to true which in turn will render an <option> with the attribute selected="selected".
Putting this together, there are two plausible options that I can see to have an option selected and use the strongly typed DropDownListFor:
Using the following view model
public class CategoriesViewModel
{
public string SelectedCategory { get; private set ; }
public ICollection<string> Categories { get; private set; }
public CategoriesViewModel(string selectedCategory, ICollection<string> categories)
{
SelectedCategory = selectedCategory;
Categories = categories;
}
}
Option 1
Set a value in the ViewData in the controller rendering your view keyed against the property name of the collection used to render the dropdown
the controller action
public class CategoriesController
{
[HttpGet]
public ViewResult Select()
{
/* some code that gets data from a datasource to populate the view model */
ICollection<string> categories = repository.getCategoriesForUser();
string selectedCategory = repository.getUsersSelectedCategory();
CategoriesViewModel model = new CategoriesViewModel(selectedCategory, categories);
this.ViewData["Categories"] = selectedCategory;
return View(model);
}
[HttpPost]
public ActionResult Select(CategoriesViewModel model)
{
/* some code that does something */
}
}
and in the strongly typed view
<%: Html.DropDownListFor(m => m.Categories, Model.Categories.Select(c => new SelectListItem { Text = c, Value = c }), new { #class = "my-css-class" }) %>
Option 2
Render the dropdown using the name of the property of the selected item(s)
the controller action
public class CategoriesController
{
[HttpGet]
public ViewResult Select()
{
/* some code that gets data from a datasource to populate the view model */
ICollection<string> categories = repository.getCategoriesForUser();
string selectedCategory = repository.getUsersSelectedCategory();
CategoriesViewModel model = new CategoriesViewModel(selectedCategory, categories);
return View(model);
}
[HttpPost]
public ActionResult Select(CategoriesViewModel model)
{
/* some code that does something */
}
}
and in the strongly typed view
<%: Html.DropDownListFor(m => m.SelectedCategory, Model.Categories.Select(c => new SelectListItem { Text = c, Value = c }), new { #class = "my-css-class" }) %>
It is confirmed as a bug # aspnet.codeplex.com
and only behaves like this for strongly typed views.
Workaround: populate your SelectList in the view code
like
<%= Html.DropDown("DocumentCategoryType", new SelectList(Model.Categories,"id","Name",Model.SelectedCategory")) =>
Yuck. I ended up solving it like this. I hope this gets fixed for RTM.
<%if(Model!=null){ %>
<%= Html.DropDownListFor(m => m.DocumentCategoryType, new SelectList(Model.DocumentCategoryTypeList,"Value","Text", Model.DocumentCategoryType))%>
<%}else{%>
<%=Html.DropDownListFor(m => m.DocumentCategoryType, Model.DocumentCategoryTypeList) %>
<%}%>
Make sure you have a value assigned to m.DocumentCategoryType when you send it to the view.
Generally this value will get reset when you do a post back so you just need to specify the value
when returning to your view.
When creating a drop down list you need to pass it two values. 1. This is where you will store the selected value 2. Is the actual List
Example
<%=Html.DropDownListFor(m => m.DocumentCategoryType, Model.DocumentCategoryTypeList) %>
I made the mistake of setting the select list item Selected value to True. This won't do anything. Instead just assign a value to m.DocumentCategoryType in your controller and this will actually do the selection for you.
Here's another good solution if the source for your drop down list is an IEnumerable instead of a SelectList:
public static SelectList MakeSelection(this IEnumerable<SelectListItem> list, object selection, string dataValueField = "Value", string dataTextField = "Text")
{
return new SelectList(list, dataValueField, dataTextField, selection);
}
Model.DocumentCategoryTypeList
This is probably your problem. On the SelectListItems, do you set the value to the .ToString() output?
var list = new List<SelectListItem>()
{
new SelectListItem()
{
Value = Category.Book.ToString(),
Text = "Book"
},
new SelectListItem()
{
Value = Category.BitsAndPieces.ToString(),
Text = "Bits And Pieces" },
new SelectListItem()
{
Value = Category.Envelope.ToString(),
Text = "Envelopes" }
};
Works for me after doing that. It just needs to be able to match the value from the object
I managed to solve the same problem by saying the folling:
new SelectList(sections.Select(s => new { Text = s.SectionName, Value = s.SectionID.ToString() }), "Value", "Text")
This trick is converting to the value to a string. I know this has been mentioned in previous answers but i find my solution a little cleaner :). Hope this helps.
Copied na pasted from my project:
<%= Html.DropDownListFor(m => m.Profession.Profession_id, new SelectList(Model.Professions, "Profession_id", "Profession_title"),"-- Profession --")%>
Model that is passed:
...
public Profession Profession { get; set; }
public IList<Profession> Professions { get; set; }
...
Html generated:
<select id="Profession_Profession_id" name="Profession.Profession_id">
<option value="">-- Profesion --</option>
<option value="4">Informatico</option>
<option selected="selected" value="5">Administracion</option>
</select>
Works for me. I have this on the form and the only disadvantage is that if model is not valid and I return the model back to the view I have to reload the list of Professions.
obj.Professions = ProfileService.GetProfessions();
return View(obj);
I also had this problem with a field ProgramName. Turns out we used ViewBag.ProgramName in the BaseController and Layout.cshtml, and this was for a different purpose. Since the value in ViewBag.ProgramName was not found in the dropdownlist, no value was selected even though the SelectListItem.Selected was true for one item in the list. We just changed the ViewBag to use a different key and the problem was resolved.
Here is a drop-in DropDownListFor replacement that varies only slightly from the original MVC source.
Example:
<%=Html.FixedDropDownListFor(m => m.DocumentCategoryType,
Model.DocumentCategoryTypeList) %>
I was worried about the performance of making so many copies of my selectList, so instead, I added the selectedvalue as a custom attribute, then used jquery to actually perform the item select:
#Html.DropDownListFor(item => item.AttendeeID, attendeeChoices, String.Empty, new { setselectedvalue = Model.AttendeeID })
........
jQuery("select[setselectedvalue]").each(function () { e = jQuery(this); e.val(e.attr("setselectedvalue")); });

Resources