MVC3 #Html.DropDownListFor not populating selected item - asp.net-mvc-3

I have the following dropdown list in an MVC3 application and the selected item is not showing a value. My dropdown list is contained in a list of a custom class. My view code is as follows.
...
for (int i = 0; i < Model.PartAttributes.Count; i++)
{
if (Model.PartAttributes[i].IsActive)
{
<div class="row inline-inputs">
<label class="span5">#Model.PartAttributes[i].DisplayName:</label>
#Html.DropDownListFor(m => m.PartAttributes[i].Value, Model.PartAttributes[i].PartList, "Choose Part")
#Html.TextBoxFor(model => Model.PartAttributes[i].Value, new { #class = "mini" })
#Html.ValidationMessageFor(model => Model.PartAttributes[i].Value)
#Html.HiddenFor(model => model.PartAttributes[i].AttributeName)
</div>
}
}
...
The text box under the dropdown box fills correctly and the list options fill correctly. And the selected option is in the list of options to pick. What can I be doing wrong?

Try like that:
#Html.DropDownListFor(
m => m.PartAttributes[i].Value,
new SelectList(
Model.PartAttributes[i].PartList,
"Value",
"Text",
Model.PartAttributes[i].Value
),
"Choose Part"
)
AFAIK the DropDownListFor helper is unable to determine the selected value from the lambda expression that is passed as first argument if this lambda expression represents complex nested properties with collections. Works with simple properties though: m => m.FooBar. I know that it kinda sucks, but hopefully this will be fixed in future versions.

Related

DropDownListFor loses list binding in validation

I am using fluentvalidation and mvc3. I have a drop down list, and it works well. I wanted to test my validation and it works EXCEPT that on validation the drop down list is empty??
What I mean is that if I purposely submit while the default SelectListItem Please Select...with a value of zero is chosen then the submit fails validation and the message shows etc. but my dropdownlist is now empty??
My controller code populating the list:
if (extforum.Count > 0)
{
foreach (var s in extforum)
model.ExternalSubscription.AvailableForums.Add(new SelectListItem(){ Text = s.ForumName, Value = s.Id.ToString() });
}
else
model.ExternalSubscription.AvailableForums.Add(new SelectListItem() { Text = "None Available", Value = "0" });
//add default value
model.ExternalSubscription.AvailableForums.Add(new SelectListItem() { Text="Please Select", Value="0", Selected=true });
My razor code:
<tr>
<td>
#Html.LabelFor(model => model.ForumName):
</td>
<td>
#Html.DropDownListFor(model => model.SelectedExtBoardId, Model.AvailableForums)
#Html.RequiredHint()
#Html.ValidationMessageFor(model => model.ExtForumBoardId)
</td>
</tr>
My validator code:
RuleFor(x => x.ExtForumBoardId)
.NotEqual(0).WithMessage("Blah"));
In your HttpPost controller action you must populate the AvailableForums collection property on your view model the same way you did in your Get action that rendered the form. This is necessary if you intend to redisplay the same view containing the dropdown. This often happens in the case of a validation error. Don't forget that when you submit a form, only the selected value of the dropdown is sent to the server. The collection of all possible values is something that you need to retrieve from your backend if you intend to redisplay the same view.

ASP.NET MVC DropDownListFor not selecting value from model

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)
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.
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)
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);
}
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)
}
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

MVC Razor - use #Html.DropDownListFor() for the 12 months of the year

I am quite new to the HTML helper extensions in MVC, let alone in Razor, and I'm looking for a simple, neat way to display a dropdown list for the 12 months of the year, with the value as the number and the text the "MMM" representation of the date.
So the HTML at the end should be like:
<select>
<option value="1">Jan</option>
<option value="2">Feb</option>
<option value="3">Mar</option>
<!-- etc -->
</select>
Is it possible to do this entirely in the view? I need an answer that users the #Html.DropDownListFor() so I can take advantage of automatic model binding. I get close with the line of code below, but it doesn't have the distinct values.
#Html.DropDownListFor(model => Model.DobMonth, new SelectList(Enumerable.Range(1,12).Select(r => new DateTime(2000, r, 1).ToString("MMM"))), "- -")
For non-technical reasons, I can't use a jQuery datepicker.
You are almost there, the problem is that if you don't provide the dataValue and dataText parameters when creating the SelectList it will just call ToString on the items and use them as the option's text.
What you need is to return Text, Value pairs from your select:
#Html.DropDownListFor(model => Model.DobMonth, new SelectList(
Enumerable.Range(1, 12)
.Select(r => new
{
Text = new DateTime(2000, r, 1).ToString("MMM"),
Value = r.ToString()
}),
"Value", "Text", Model.DobMonth))
If you want to have an "empty" item you need to add it manually:
#Html.DropDownListFor(model => Model.DobMonth, new SelectList(
new [] { new { Text = "- -", Value = (string)null } }.Concat(
Enumerable.Range(1, 12)
.Select(r => new
{
Text = new DateTime(2000, r, 1).ToString("MMM"),
Value = r.ToString()
})),
"Value", "Text", Model.DobMonth))

Validation of DropDownListFor is not working in MVC3?

Validation is Working on Other Input type text element but not working on DropDownListFor
Class Purchase Input Property Code
[Required]
public string LedgerId { get; set; }
Class View Model Code
PurchaseViewModel purchaseVM = new PurchaseViewModel
{
// PurchaseInput=purchaseInput,
Ledger = uw.LedgerRepository.Get().Select(x => new SelectListItem { Value = x.Id.ToString(), Text = x.LedgerName }),
};
View
<div class="column">
<div class="labelField">
#Html.LabelFor(model => model.PurchaseInput.LedgerId, "Party")
</div>
<div class="ItemField">
#Html.DropDownListFor(model => model.PurchaseInput.LedgerId, new SelectList(Model.Ledger, "Value", "Text"))
#Html.ValidationMessageFor(model => model.PurchaseInput.LedgerId)
</div>
</div>
On the face of it, it seems that you do not have an empty item in your select list. The validation will only trigger if the user selects a dropdown item with string length of zero. If you examine the Html source can you see the validation attributes on the dropdown ( depending on whether you are using unobtrusive validation or not)?
Yes, there are problems with validation of DropDownListFor. look at this link. They get validation data manually from metadata - http://forums.asp.net/t/1649193.aspx
Although this is a workaround, at least it fires some sort of validation. Try:
#Html.DropDownListFor(model => model.PurchaseInput.LedgerId, new SelectList(Model.Ledger, "Value", "Text"), new { #class = "required" })

Selecting an alternate EditorFor template for a List

I have an object that represents a food item to order at a restaurant. This object has a list of Modifier Groups (sides, cooking instructions, pizza toppings, whatever) and each list has a list of Modifiers.
Certain Modifier options need to be displayed differently (for example, toppings need to specify left/right/all), even though they are the same data type.
I am trying use #Html.EditorFor and specify the alternate EditorTemplate when required.
In /Views/Shared/EditorTemplates I have ModifierSelection.cshtml and ToppingSelection.cshtml. I am calling them in the following view:
#model MyApp.ViewModels.ModifierSelectionList
<div class="menugroup">
<h3 class="menuname">#Model.ModifierListName: (Select #Model.MaximumSelections)</h3>
<div class="modcountvalidation">#Model.ValidationResults</div>
#Html.HiddenFor(model => Model.ModifierListId)
<table class="menu">
#if (Model.IsToppingsList)
{
#Html.EditorFor(model => Model.ModifierSelections, "ToppingSelection")
}
else
{
#Html.EditorFor(model => Model.ModifierSelections)
}
</table>
</div>
When I try to display an item that requires the "ToppingSelection" EditorTemplate instead of the default, I get the following error:
System.InvalidOperationException was unhandled by user code
Message=The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[MyApp.ViewModels.ModifierSelection]', but this dictionary requires a model item of type 'MyApp.ViewModels.ModifierSelection'.
Source=System.Web.Mvc
So - I have a set of EditorTemplates for a data type. I am trying to use them to display a list of items and I need to be able to select which one to use.
What am I doing wrong?
Thanks!
OK, here is the real solution. Rather than iterating through the list using foreach, I had to iterate using a for loop.
#for (int i = 0; i < Model.ModifierSelections.Count; i++ )
{
if (Model.IsToppingsList)
{
#Html.EditorFor(m => Model.ModifierSelections[i], "ToppingSelection")
}
else
{
#Html.EditorFor(m => Model.ModifierSelections[i])
}
}
Solved!
Apparently, if you send a list type to Html.EditorFor and do not specify a template, it will iterate through the list and display each item using the template that it finds for the item type. If you do specify a template, it will not iterate through the list and send each item to that template, it will attempt to send the entire list to your template, which isn't the right data type.
I fixed it by manually iterating through the list:
#foreach (var modifierSelection in Model.ModifierSelections)
{
if (Model.IsToppingsList)
{
#Html.EditorFor(m => modifierSelection, "ToppingSelection")
}
else
{
#Html.EditorFor(m => modifierSelection)
}
}

Resources