10

I'm developing an ASP.NET MVC 5 application, with C# and .NET Framework 4.6.1.

I have this View:

@model MyProject.Web.API.Models.AggregationLevelConfViewModel

[...]

@Html.DropDownListFor(m => m.Configurations[0].HelperCodeType, (SelectList)Model.HelperCodeTypeItems, new { id = "Configurations[0].HelperCodeType" })

The ViewModel is:

public class AggregationLevelConfViewModel
{
    private readonly List<GenericIdNameType> codeTypes;
    private readonly List<GenericIdNameType> helperCodeTypes;

    public IEnumerable<SelectListItem> CodeTypeItems
    {
        get { return new SelectList(codeTypes, "Id", "Name"); }
    }

    public IEnumerable<SelectListItem> HelperCodeTypeItems
    {
        get { return new SelectList(helperCodeTypes, "Id", "Name"); }
    }

    public int ProductionOrderId { get; set; }

    public string ProductionOrderName { get; set; }

    public IList<Models.AggregationLevelConfiguration> Configurations { get; set; }

    public AggregationLevelConfViewModel()
    {
        // Load CodeTypes to show it as a DropDownList
        byte[] values = (byte[])Enum.GetValues(typeof(CodeTypes));

        codeTypes = new List<GenericIdNameType>();
        helperCodeTypes = new List<GenericIdNameType>();

        for (int i = 0; i < values.Length; i++)
        {
            GenericIdNameType cType = new GenericIdNameType()
            {
                Id = values[i].ToString(),
                Name = EnumHelper.GetDescription((CodeTypes)values[i])
            };

            if (((CodeTypes)values[i]) != CodeTypes.NotUsed)
                codeTypes.Add(cType);

            helperCodeTypes.Add(cType);
        }
    }
}

And Models.AggregationLevelConfiguration is:

public class AggregationLevelConfiguration
{
    public byte AggregationLevelConfigurationId { get; set; }
    public int ProductionOrderId { get; set; }
    public string Name { get; set; }
    public byte CodeType { get; set; }
    public byte HelperCodeType { get; set; }
    public int PkgRatio { get; set; }
    public int RemainingCodes { get; set; }
}

I need to set selected value in these properties:

public IEnumerable<SelectListItem> CodeTypeItems
{
    get { return new SelectList(codeTypes, "Id", "Name"); }
}

public IEnumerable<SelectListItem> HelperCodeTypeItems
{
    get { return new SelectList(helperCodeTypes, "Id", "Name"); }
}

But I can't set it in new SelectList(codeTypes, "Id", "Name"); or new SelectList(helperCodeTypes, "Id", "Name"); because the selected value are in Configurations array: fields AggregationLevelConfiguration.CodeType and AggregationLevelConfiguration.HelperCodeType.

I think I have to set selected value in the View, but I don't know how to do it.

How can I set the selected values?

VansFannel
  • 45,055
  • 107
  • 359
  • 626
  • Its an unfortunate limitation of using `DropDownListFor()` in a loop. You need to generate a new `MutliSelectList` in each iteration (setting the `selectedValues` property in the constructor), or (better), use an `EditorTemplate` for typeof `AggregationLevelConfiguration` and pass the `SelectList` to the template as `AdditionalViewData` –  May 24 '16 at 08:14
  • Thanks. I have understood that I have to use things but I don't know how to use them. – VansFannel May 24 '16 at 08:25
  • I'm trying to find an answer I gave about 6 months ago with the code for both options but not having any luck. If I cannot find it, I'll add an answer in an hour or so - but the following should work - `@Html.DropDownListFor(m => m.Configurations[0].HelperCodeType, new SelectList(Model.HelperCodeTypeItems, "Value", "Text", Model.Configurations[0].HelperCodeType, new { ... })` –  May 24 '16 at 08:30
  • I'm working on a solution using jQuery to select the option. – VansFannel May 24 '16 at 08:32
  • Yes, that works. Thanks. – VansFannel May 24 '16 at 08:35
  • Its not really necessary to create a `SelectList` in the controller using that option - delete your `CodeTypeItems` and `HelperCodeTypeItems` and make the `codeTypes` and `helperCodeTypes` public properties, then it would be `@Html.DropDownListFor(m => m.Configurations[0].HelperCodeType, new SelectList(Model.codeTypes, "Id", "Name", Model.Configurations[0].HelperCodeType, new { ... })` –  May 24 '16 at 08:37

3 Answers3

20

Unfortunately @Html.DropDownListFor() behaves a little differently than other helpers when rendering controls in a loop. This has been previously reported as an issue on CodePlex (not sure if its a bug or just a limitation)

The are 2 option to solve this to ensure the correct option is selected based on the model property

Option 1 (using an EditorTemplate)

Create a custom EditorTemplate for the type in the collection. Create a partial in /Views/Shared/EditorTemplates/AggregationLevelConfiguration.cshtml (note the name must match the name of the type

@model yourAssembly.AggregationLevelConfiguration
@Html.DropDownListFor(m => m.HelperCodeType, (SelectList)ViewData["CodeTypeItems"])
.... // other properties of AggregationLevelConfiguration

and then in the main view, pass the SelectList to the EditorTemplate as additionalViewData

@using (Html.BeginForm())
{
  ...
  @Html.EditorFor(m => m.Configurations , new { CodeTypeItems = Model.CodeTypeItems })
  ...

Option 2 (generate a new SelectList in each iteration and set the selectedValue)

In this option your property CodeTypeItems should to be IEnumerable<GenericIdNameType>, not a SelectList (or just make codeTypes a public property). Then in the main view

@Html.DropDownListFor(m => m.Configurations[0].HelperCodeType, new SelectList(Model.CodeTypeItems, "Id", "Name", Model.Configurations[0].HelperCodeType)

Side note: there is no need to use new { id = "Configurations[0].HelperCodeType" - the DropDownListFor() method already generated that id attribute

  • With option 2 you saved my day! Before, I used a hacky JavaScript loop to iterate through all my DropDownLists and set the selected value manually from hidden fields. – BergListe Dec 04 '17 at 11:13
  • Thanks. I'll just say it: MVC sucks. I'm glad we have other options now!! – Mmm Nov 29 '22 at 17:11
0

I wrote this class to overcome an issue I was having with selecting an option in an html select list. I hope it helps someone.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;

namespace Login_page.Models
{
    public class HTMLSelect
    {
        public string id { get; set; }
        public IEnumerable<string> @class { get; set; }
        public string name { get; set; }
        public Boolean required { get; set; }
        public string size { get; set; }
        public IEnumerable<SelectOption> SelectOptions { get; set; }

        public HTMLSelect(IEnumerable<SelectOption> options)
        {

        }

        public HTMLSelect(string id, string name)
        {
            this.id = id;
            this.name = name;
        }

        public HTMLSelect(string id, string name, bool required, IEnumerable<SelectOption> options)
        {
            this.id = id;
            this.name = name;
            this.required = required;
        }

        private string BuildOpeningTag()
        {
            StringBuilder text = new StringBuilder();
            text.Append("<select");
            text.Append(this.id != null ? " id=" + '"' + this.id + '"' : "");
            text.Append(this.name != null ? " name=" + '"' + this.name + '"' : "");
            text.Append(">");
            return text.ToString();

        }

        public string GenerateSelect(IEnumerable<SelectOption> options)
        {
            StringBuilder selectElement = new StringBuilder();
            selectElement.Append(this.BuildOpeningTag());
            foreach (SelectOption option in options)
            {
                StringBuilder text = new StringBuilder();
                text.Append("\t");
                text.Append("<option value=" + '"' + option.Value + '"');
                text.Append(option.Selected != false ? " selected=" + '"' + "selected" + '"' + ">" : ">");
                text.Append(option.Text);
                text.Append("</option>");
                selectElement.Append(text.ToString());
            }
            selectElement.Append("</select");
            return selectElement.ToString();
        }
    }

    public class SelectOption
    {
        public string Text { get; set; }
        public Boolean Selected { get; set; }
        public string Value { get; set; }
    }
}

And

public IEnumerable<SelectOption> getOrderTypes()
{
    List<SelectOption> orderTypes = new List<SelectOption>();
                        if (this.orderType == "OptionText")
                        {
                            orderTypes.Add(new SelectOption() { Value = "1", Text = "OptionText", Selected = true });
                        } else
                        {
                            orderTypes.Add(new SelectOption() { Value = "2", Text = "OptionText2" });
                        }
}

And to use it:

@{
    Login_page.Models.HTMLSelect selectElement = new Login_page.Models.HTMLSelect("order-types", "order-types");

}
@Html.Raw(selectElement.GenerateSelect(Model.getOrderTypes()));
Brandon Minnick
  • 13,342
  • 15
  • 65
  • 123
JasonG
  • 127
  • 1
  • 8
0

I leave this in case it helps someone else. I had a very similar problem and none of the answers helped.

We had in a view this line at the top:

IEnumerable<SelectListItem> exitFromTrustDeed = (ViewData["ExitFromTrustDeed"] as IEnumerable<string>).Select(e => new SelectListItem() { 
    Value = e, 
    Text = e,
    Selected = Model.ExitFromTrustDeed == e
});

and then below in the view:

@Html.DropDownListFor(m => m.ExitFromTrustDeed, exitFromTrustDeed, new { @class = "form-control" })

We had a property in my ViewData with the same name as the selector for the lambda expression and for some reason that makes the dropdown to be rendered without any option selected.

We changed the name in ViewData to ViewData["ExitFromTrustDeed2"] and that made it work as expected.

Weird though.

Alberto Sadoc
  • 107
  • 2
  • 5