Selected property in SelectListItem never works (D

2019-06-20 08:44发布

问题:

I have problems when selecting a value of a DropDownList. I've been reading all the similar posts and I can't get a solution.

The actual approach seemed very good to me, because I can inspect the fields that will be inside the SelectList:

var selectList = new List<SelectListItem>(
    from variable in someKindOfCollection
    select new SelectListItem
        {
            Selected = variable.Property == selection,
            Text = variable.Property,
            Value = variable.Property
        });

Supposedly, this gives me total control. After the selectList has been built I can inspect the variables with the debugger. Everything is OK and one of them has the "Selected" attribute marked.

Then I use DropDownListFor in order to show on the view:

@Html.DropDownListFor(
    g => g.SomePropertyInModel , selectList, new { @class = "cssClass" })

But It doesn't work, never... "Renders" the dropdown, but nothing is selected.

Thanks a lot :)

NEW EXAMPLE First of all I want to apologize. I've been hiding information, of course totally unintentionally. All the code happens inside a Razor For Loop:

@foreach (var loopVariable in Model.Collection)
{
    if (Model.SomeCondition != null)
    {
        selection = someValue;
    }

    var selectList = new List<SelectListItem>(
      from variable in someKindOfCollection
      select new SelectListItem
    {
        Selected = variable.Property == selection,
        Text = variable.Property,
        Value = variable.Property
    });

    @Html.DropDownListFor(
        g => g.SomePropertyInModel , selectList, new { @class = "cssClass" })

}

So, It's the fact of selectList is a local variable causing the behavior?. Sorry, I didn't thought it was that.

回答1:

I think you are having the same problem i did. I looked through the source code to find my solution and it appears this is a bug to me. The following DropDownListFor should help, the key to it is that you pass the selected value into the html helper. Hope this helps.

public static class SelectExtensions {
    public static IHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList, string selectedValue, string optionLabel, object htmlAttributes = null) {
       return DropDownListHelper(helper, ExpressionHelper.GetExpressionText(expression), selectList, selectedValue, optionLabel, htmlAttributes);
    }

    /// <summary>
    /// This is almost identical to the one in ASP.NET MVC 3 however it removes the default values stuff so that the Selected property of the SelectListItem class actually works
    /// </summary>
   private static IHtmlString DropDownListHelper(HtmlHelper helper, string name, IEnumerable<SelectListItem> selectList, string selectedValue, string optionLabel, object htmlAttributes) {
        name = helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);

        // Convert each ListItem to an option tag
        var listItemBuilder = new StringBuilder();

        // Make optionLabel the first item that gets rendered
        if (optionLabel != null)
            listItemBuilder.AppendLine(ListItemToOption(new SelectListItem() { Text = optionLabel, Value = String.Empty, Selected = false }, selectedValue));

        // Add the other options
        foreach (var item in selectList) {
            listItemBuilder.AppendLine(ListItemToOption(item, selectedValue));
        }

        // Now add the select tag
        var tag = new TagBuilder("select") { InnerHtml = listItemBuilder.ToString() };
        tag.MergeAttributes(HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
        tag.MergeAttribute("name", name, true);
        tag.GenerateId(name);

        // If there are any errors for a named field, we add the css attribute
        ModelState modelState;

        if (helper.ViewData.ModelState.TryGetValue(name, out modelState)) {
            if (modelState.Errors.Count > 0)
                tag.AddCssClass(HtmlHelper.ValidationInputCssClassName);
        }

        // Add the unobtrusive validation attributes
        tag.MergeAttributes(helper.GetUnobtrusiveValidationAttributes(name));

        return tag.ToHtmlString(TagRenderMode.Normal);
    }

    private static string ListItemToOption(SelectListItem item, string selectedValue) {
        var tag = new TagBuilder("option") { InnerHtml = HttpUtility.HtmlEncode(item.Text) };

        if (item.Value != null)
            tag.Attributes["value"] = item.Value;

        if ((!string.IsNullOrEmpty(selectedValue) && item.Value == selectedValue) || item.Selected)
            tag.Attributes["selected"] = "selected";

        return tag.ToString(TagRenderMode.Normal);
    }
}


回答2:

I'd recommend the usage of a view model:

public ActionResult Index()
{
    var model = new MyViewModel
    {
        // this will preselect the item with value = "2.400 €" in the collection
        SelectedValue = "2.400 €",

        Values = someKindOfCollection
    };
    return View(model);
}

and in the view:

@Html.DropDownListFor(
    g => g.SelectedValue, 
    new SelectList(Model.Values, "SomePropertyForValue", "SomePropertyForText"), 
    new { @class = "cssClass" }
)


回答3:

You should debug your select list again.

None of your option values have a selected=selected on them.

Try hard coding your list to something like

SelectList = new List<SelectListItem>
        {
            new SelectListItem{Text = "600 €", Value="600 €" , Selected = false},
            new SelectListItem{Text = "1.200 €", Value="1.200 €" , Selected = false},
            new SelectListItem{Text = "2.400 €", Value="2.400 €" , Selected = false},
            new SelectListItem{Text = "3.000 €", Value="3.000 €" , Selected = true},
        };

Using this code, and your Html.DropDownFor the SelectList works fine and generates this HTML

<select id="TheID" name="TheName" class="cssClass">
  <option value="600 €">600 €</option>
  <option value="1.200 €">1.200 €</option>
  <option value="2.400 €">2.400 €</option>
  <option value="3.000 €" selected="selected">3.000 €</option>
</select>

From your code, I can't tell if your comparison is setting one of the Selected attributes to true or not, but that is probably why your code is not working.



回答4:

I don't know if this will help anyone, but this solved it for me....

I had both the collection and the selected value in my model.

I changed my view from:

@Html.DropDownListFor(
g => g.CollectionName, 
new SelectList(Model.CollectionName, "SomePropertyForValue", "SomePropertyForText", Model.SelectedValue), 
new { @class = "cssClass" }
)

to

@Html.DropDownListFor(
g => g.SelectedValue, 
new SelectList(Model.CollectionName, "SomePropertyForValue", "SomePropertyForText", Model.SelectedValue), 
new { @class = "cssClass" }
)

When my first parameter to the DropDownListFor referenced the collection, I would get the drop down list but the selected value would never stick.



回答5:

The solution for this problem is simpler that we all think...

All we need to do, when return the view from the controller, is set the property on the view model for the element that the dropdown is bound to - i.e: model.SomePropertyInModel = '5' for example

this way when we do this

@Html.DropDownListFor(
g => g.SomePropertyInModel , selectList, new { @class = "cssClass" })

the HtmlHelper will automatically pick up the default value to display on the DropDownList

simples!

Hope it may help you and all the others - like me! - that have lost a lot of time searching for a solution for this apparent issue.



回答6:

I had a similar problem and the cause was that I was not setting the value for the property on my view model that was sourced by the SelectListItems. You actually don't need to set the "Selected" property at all. To use you the wording from the original example, just set "SomePropertyOnModel" to whatever the current selection is. DropDownListFor will bind the "Selected" property appropriately.



回答7:

My recommendation for DropDownListFor is,

  • have an IEnumerable property the model (or viewmodel) that is being passed to the View. In your case for example:

    private IEnumerable<SelectListItem> GetListItems() 
    {
        var selectList = db.YourDBThings
          .Where(t => t.IsCool == true)
          .Select(x => new SelectListItem
              {
                Value = x.Id.ToString(),
                Text = x.Name
              });
        return new SelectList(issues, "Value", "Text");
    }
    
  • And then add this IEnumerable to your object, assuming you also have a property in your object that has ONE item (the one that will be set as selected) that matches one of the "value" in the list:

    FancyViewModel myFancyViewModel = new FancyViewModel();
    myFancyViewModel.ListOptions = GetListItems();
    myFancyViewModel.SelectedThingId = 3; //this 3 would match one of the value in the list
    //....
    return View(myFancyViewModel);
    
  • Then in the view where you receive the FancyViewModel instance that contains the list of items:

    @model Project.ViewModels.FancyViewModel
    @*...*@
    @Html.DropDownListFor(x=>x.SelectedThingId, Model.ListOptions)
    

The first argument in DropDownListFor specifies the TProperty that contains the field that will be selected within the list. The second is the list itself that comes from the model (although it could be retrieved from anywhere!)



回答8:

There is a problem, g.SelectedValue must be the nullable type in your model.