28

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?

Andrew Flanagan
  • 4,267
  • 3
  • 25
  • 37
  • 2
    Instead of setting multiple on Html.DropDownList, you can use Html.ListBoxFor. Since Html.DropDownList will only set one of the items to be selected. – Nikita Ignatov Oct 30 '12 at 18:08
  • @NikitaIgnatov Thanks. your comment really help me a lot. – gfan Sep 26 '15 at 07:17

6 Answers6

53

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.

Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • Darin Dimitrov thanks for your all helps.. You are everywhere.. :) – yusuf May 23 '13 at 08:06
  • Did something maybe change in MVC5 because this exact answer doesn't work anymore. – Roger Far Jul 06 '14 at 04:34
  • It works fine in MVC5. The important thing is to set the selected items in the model which will be passed to the view. – qurban May 19 '15 at 10:48
  • 7
    This should be the preferred answer. The key point is to use Html.ListBoxFor – AntonK Mar 09 '16 at 02:00
  • 1
    Be aware not to set the same name for the selected and all elements. This will not work Html.ListBoxFor(x => x.Items, new MultiSelectList(ViewBag.Items, "Id", "Name")) – gyosifov Dec 13 '16 at 16:18
  • I was trying to use Html.Dropdown list, but was facing weird issues with multiple selection. Then I read your comment about using Html.ListBox which solved my problem. Thanks for that! – Ali Shah Ahmed May 15 '17 at 05:04
23

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

Leniel Maccaferri
  • 100,159
  • 46
  • 371
  • 480
Jero
  • 1,081
  • 1
  • 8
  • 11
6

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.

Thorsten Westheider
  • 10,572
  • 14
  • 55
  • 97
5

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());
    }
Aviko
  • 1,139
  • 12
  • 21
2

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" })
Liron
  • 2,012
  • 19
  • 39
0

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 (,)

Josue Morales
  • 711
  • 7
  • 6