16

I'm using ASP.NET MVC 3, and just ran into a 'gotcha' using the DropDownListFor HTML Helper.

I do this in my Controller:

ViewBag.ShippingTypes = this.SelectListDataRepository.GetShippingTypes();

And the GetShippingTypes method:

public SelectList GetShippingTypes()
{
    List<ShippingTypeDto> shippingTypes = this._orderService.GetShippingTypes();

    return new SelectList(shippingTypes, "Id", "Name");
}

The reason I put it in the ViewBag and not in the model (I have strongly typed models for each view), is that I have a collection of items that renders using an EditorTemplate, which also needs to access the ShippingTypes select list.

Otherwise I need to loop through the entire collection, and assign a ShippingTypes property then.

So far so good.

In my view, I do this:

@Html.DropDownListFor(m => m.RequiredShippingTypeId, ViewBag.ShippingTypes as SelectList)

(RequiredShippingTypeId is of type Int32)

What happens is, that the value of RequiredShippingTypeId is not selected in the drop down.

I came across this: http://web.archive.org/web/20090628135923/http://blog.benhartonline.com/post/2008/11/24/ASPNET-MVC-SelectList-selectedValue-Gotcha.aspx

He suggests that MVC will lookup the selected value from ViewData, when the select list is from ViewData. I'm not sure this is the case anymore, since the blog post is old and he's talking about MVC 1 beta.

A workaround that solves this issue is this:

@Html.DropDownListFor(m => m.RequiredShippingTypeId, new SelectList(ViewBag.ShippingTypes as IEnumerable<SelectListItem>, "Value", "Text", Model.RequiredShippingTypeId.ToString()))

I tried not to ToString on RequiredShippingTypeId at the end, which gives me the same behavior as before: No item selected.

I'm thinking this is a datatype issue. Ultimately, the HTML helper is comparing strings (in the Select List) with the Int32 (from the RequiredShippingTypeId).

But why does it not work when putting the SelectList in the ViewBag -- when it works perfectly when adding it to a model, and doing this inside the view:

@Html.DropDownListFor(m => m.Product.RequiredShippingTypeId, Model.ShippingTypes)
1
  • Thanks for the workaround! So non-obvious that the magic only works with "simple" lambda expressions, and the system gives no warning about it. Commented Sep 12, 2015 at 15:59

5 Answers 5

32

The reason why this doesn't work is because of a limitation of the DropDownListFor helper: it is able to infer the selected value using the lambda expression passed as first argument only if this lambda expression is a simple property access expression. For example this doesn't work with array indexer access expressions which is your case because of the editor template.

You basically have (excluding the editor template):

@Html.DropDownListFor(
    m => m.ShippingTypes[i].RequiredShippingTypeId, 
    ViewBag.ShippingTypes as IEnumerable<SelectListItem>
)

The following is not supported: m => m.ShippingTypes[i].RequiredShippingTypeId. It works only with simple property access expressions but not with indexed collection access.

The workaround you have found is the correct way to solve this problem, by explicitly passing the selected value when building the SelectList.

Sign up to request clarification or add additional context in comments.

3 Comments

Damn, I didn't know that. I can confirm that this is indeed the case. I tried to use the ShippingTypes from the ViewBag, instead of the model for the drop down that takes the general shipping type: @Html.DropDownListFor(m => m.Product.RequiredShippingTypeId, ViewBag.ShippingTypes as SelectList) -- and that works. So, I guess I have to stick to the ugly workaround, might write an HTML helper for that, instead of leaving dirty hacks in my views.
@Darin - can you point me to the documentation for this? Thanks.
This seems to also apply if you are using some other object/alias to get the value from e.g. @Html.DropDownListFor(m => myViewModel.RequiredShippingTypeID, .... Thanks!
0

This might be silly, but does adding it to a variable in your view do anything?

var shippingTypes = ViewBag.ShippingTypes;

@Html.DropDownListFor(m => m.Product.RequiredShippingTypeId, shippingTypes)

1 Comment

k cool just thought I'd suggest it as a possible "cleaner" workaround.
0

you can create dynamic viewdata instead of viewbag for each dropdownlist field for complex type. hope this will give you hint how to do that

      @if (Model.Exchange != null)
                                {
                                    for (int i = 0; i < Model.Exchange.Count; i++)
                                    {
                                        <tr>
                                            @Html.HiddenFor(model => model.Exchange[i].companyExchangeDtlsId)
                                            <td>
                                                 
                                                @Html.DropDownListFor(model => model.Exchange[i].categoryDetailsId, ViewData["Exchange" + i] as SelectList, " Select category", new { @id = "ddlexchange", @class = "form-control custom-form-control required" })
                                                @Html.ValidationMessageFor(model => model.Exchange[i].categoryDetailsId, "", new { @class = "text-danger" })
                                            </td>
                                            <td>
                                                @Html.TextAreaFor(model => model.Exchange[i].Address, new { @class = "form-control custom-form-control", @style = "margin:5px;display:inline" })
                                            
                                                @Html.ValidationMessageFor(model => model.Exchange[i].Address, "", new { @class = "text-danger" })
                                            </td>
                                        </tr>
                                    }

                                }

  ViewModel CompanyDetail = companyDetailService.GetCompanyDetails(id);
               if (CompanyDetail.Exchange != null)
                for (int i = 0; i < CompanyDetail.Exchange.Count; i++)
                {
                    ViewData["Exchange" + i]= new SelectList(companyDetailService.GetComapnyExchange(), "categoryDetailsId", "LOV", CompanyDetail.Exchange[i].categoryDetailsId);
                }

Comments

0

I was just hit by this limitation and figured out a simple workaround. Just defined extension method that internally generates SelectList with correct selected item.

public static class HtmlHelperExtensions
{
    public static MvcHtmlString DropDownListForEx<TModel, TProperty>(
       this HtmlHelper<TModel> htmlHelper,
       Expression<Func<TModel, TProperty>> expression,
       IEnumerable<SelectListItem> selectList,
       object htmlAttributes = null)
    {
        var selectedValue = expression.Compile().Invoke(htmlHelper.ViewData.Model);
        var selectListCopy = new SelectList(selectList.ToList(), nameof(SelectListItem.Value), nameof(SelectListItem.Text), selectedValue);

        return htmlHelper.DropDownListFor(expression, selectListCopy, htmlAttributes);
    }
}

The best thing is that this extension can be used the same way as original DropDownListFor:

@for(var i = 0; i < Model.Items.Count(); i++)
{
   @Html.DropDownListForEx(x => x.Items[i].CountryId, Model.AllCountries)
}

Comments

0

There is an overloaded method for @html.DropdownList for to handle this. There is an alternative to set the selected value on the HTML Dropdown List.

@Html.DropDownListFor(m => m.Section[b].State, 
              new SelectList(Model.StatesDropdown, "value", "text", Model.Section[b].State))

I was able to get the selected value from the model.

"value", "text", Model.Section[b].State this section the above syntax adds the selected attribute to the value loaded from the Controller

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.