MVC 3 #Html.DropDownListFor model binding fails - asp.net-mvc-3

I am using VS 2010 with MVC 3 and EF 5. I am using a common pattern for dropdown lists that works correctly in all but one instance and I cannot see why this one is failing to select the correct entry in the select list. The following are code snippets.
The select list is created as follows:
public static IEnumerable<SelectListItem> GetOutcomes()
{
CodesEntities dataContextCodes = new CodesEntities(ConnectionString);
return new SelectList(dataContextCodes.CodeOutcome.
Where(x => x.DisplayOrder > 0).OrderBy(x => x.DisplayOrder),
"OutcomeCodeID", "Outcome");
}
This returns the correct select list.
The view has the following code:
#Html.DropDownListFor(m => m.OutcomeCodeID,
PerintalFormViewModels.GetOutcomes(), "Please select an item")
The model value m.OutcomeCodeID has a valid value (1) but no item is being selected.
The generated HTML is:
<select id="CodeID" name="OutcomeCodeID" data-val-required="Outcome is required" data-val-number="The field outcome must be a number." data-val="true">
<option value="">Please select an item</option>
<option value="1">Termination</option>
<option value="2">Loss</option>
<option value="3">Still</option>
<option value="4">Live</option>
</select>
I am in the hair tearing out, being driven nuts stage. Does anyone have any thoughts?
Thanks

You can do it like this:
public static IEnumerable<SelectListItem> GetOutcomes(string selectedID)
{
CodesEntities dataContextCodes = new CodesEntities(ConnectionString);
return new SelectList(dataContextCodes.CodeOutcome.
Where(x => x.DisplayOrder > 0).OrderBy(x => x.DisplayOrder),
"OutcomeCodeID", "Outcome"
, selectedID); // add this parameter
}
Or this:
public static IEnumerable<SelectListItem> GetOutcomes(string selectedID)
{
CodesEntities dataContextCodes = new CodesEntities(ConnectionString);
return
dataContextCodes.CodeOutCome
.Where(x => x.DisplayOrder > 0)
.OrderBy(x => x.DisplayOrder)
.ToList()
.Select(x => new SelectListItem
{
Value = x.OutcomeCodeID.ToString(),
Text = x.Outcome.ToString(),
Selected = x.OutcomeCodeID == selectedID
});
}
Then call it like this:
#Html.DropDownListFor(m => m.OutcomeCodeID,
PerintalFormViewModels.GetOutcomes(Model.OutcomeCodeID),
"Please select an item")

You are not setting the default selected value anywhere.
The particular constructor you are using for the SelectList is this one:
public SelectList(
IEnumerable items,
string dataValueField,
string dataTextField
)
Which does not set the default value. Either you use this one:
public SelectList(
IEnumerable items,
string dataValueField,
string dataTextField,
Object selectedValue
)
And specify the selectedValue or you manually set the SelectedListItem's Selected property to true on the item you want selected (http://msdn.microsoft.com/en-us/library/system.web.mvc.selectlistitem(v=vs.108).aspx).
It's a shameless plug but still I think it's a good resource: http://blinkingcaret.wordpress.com/2012/08/11/using-html-dropdownlistfor/

Related

Blazor Dropdown does not keep bound value when options change

I have the below code in a new Blazor Server project, just a single page with this on.
#page "/"
#using BlazorApp1.Data;
<select #bind="selectedValue1">
#foreach (DropdownOption option in notValue(0))
{
<option value="#option.value" aria-label="#option"> #option.displayName </option>
}
</select>
<select #bind="selectedValue2">
#foreach (DropdownOption option in notValue(1))
{
<option value="#option.value" aria-label="#option"> #option.displayName </option>
}
</select>
<select #bind="selectedValue3">
#foreach (DropdownOption option in notValue(2))
{
<option value="#option.value" aria-label="#option"> #option.displayName </option>
}
</select>
#code {
List<DropdownOption> test = new List<DropdownOption>()
{
new("BOB", "BOB"),
new("GEOFF", "GEOFF"),
new("GREGROY", "GREGORY"),
new("GERTRUDE", "GERTRUDE"),
new("NameyMcNameFace", "NameyMcNameFace"),
new("GILES", "GILES")
};
List<DropdownOption> notValue(int index) => GetValues(index);
string selectedValue1;
string selectedValue2;
string selectedValue3;
private List<DropdownOption> GetValues(int index)
{
var selected = new List<string>() { selectedValue1, selectedValue2, selectedValue3 };
selected = selected.Select((x, i) => i == index ? "" : x).ToList();
return test.Where(x => !selected.Contains(x.value)).ToList();
}
//public readonly record struct DropdownOption(string value, string displayName, string? group = null)
//{
//}
}
The commented out bit of code is a helper to manage dropdowns and keep it type safe and is simply in another file.
Behaviour happens if you the following steps.
Select dropdowns to
Bob
Gertrude
Giles
Then change the third dropdown from Giles to Geoff and the second dropdown changes value even though it is bound and Gertrude is still an option in the second dropdown.
Is anyone able to explain this behaviour?
Scrrenshot showing behaviour (do not change second dropdown)
See how second dropdown value chnges without being changes manually and it still binds to correct string in backend
Your problem is in the way you are using indexes. Take the first select. You select a value say Gregory - that's index 2 in the list provided. You then select Geoff in the second select. The first select hasn't changed, so it doesn't update, yet you've removed index 1 from it's list. Gregory is now at index 1, but the select thinks the selected item is index 2. You'll find the underlying value is correct, but not the displayed value.
Your second problem is that by default Bob is selected, but the underlying value isn't set. You need to use something like the construct I show in the component to show an unselectable message.
Hope you don't mind, but I've taken the liberty to redesign what you have to iron out these issues.
First a component for your select to encapsulate the functionality. I've added a -- Select a Person -- option that only shows when nothing is selected.
<select class="form-select mb-3" value="#Value" #onchange="(e) => SetValue(e, this.Index)">
#if (this.Selected is null)
{
<option value="" selected disabled aria-label="not selected"> -- Select a Person -- </option>
}
#foreach (DropdownOption option in Items )
{
<option #key="option" value="#option.displayName" aria-label="#option.displayName"> #option.displayName </option>
}
</select>
#code {
private string? Value => Selected?.displayName ?? null;
[Parameter,EditorRequired] public DropdownOption? Selected { get; set; }
[Parameter, EditorRequired] public int Index { get; set; }
[Parameter, EditorRequired] public EventCallback<Tuple<string?, int>> ValueChanged { get; set; }
[Parameter, EditorRequired] public IEnumerable<DropdownOption> Items { get; set; } = Enumerable.Empty<DropdownOption>();
private Task SetValue(ChangeEventArgs e, int index)
{
var value = e.Value?.ToString() ?? null;
this.ValueChanged.InvokeAsync(new Tuple<string?, int>(value, index));
return Task.CompletedTask;
}
}
And then your page:
#page "/"
<MySelect Index="1" Selected="selectedValue1" Items="GetValues(1)" ValueChanged="this.OnValueChanged" />
<MySelect Index="2" Selected="selectedValue2" Items="GetValues(2)" ValueChanged="this.OnValueChanged" />
<MySelect Index="3" Selected="selectedValue3" Items="GetValues(3)" ValueChanged="this.OnValueChanged" />
#code {
private DropdownOption? selectedValue1;
private DropdownOption? selectedValue2;
private DropdownOption? selectedValue3;
private void OnValueChanged(Tuple<string?, int> tuple)
{
var value = tuple.Item1;
var index = tuple.Item2;
if (value is null)
return;
if (index == 1)
selectedValue1 = test.SingleOrDefault(item => item.displayName.Equals(value));
if (index == 2)
selectedValue2 = test.SingleOrDefault(item => item.displayName.Equals(value));
if (index == 3)
selectedValue3 = test.SingleOrDefault(item => item.displayName.Equals(value));
}
private IEnumerable<DropdownOption> GetValues(int index)
{
var selected = new List<DropdownOption>();
if (selectedValue1 is not null && index != 1)
selected.Add((DropdownOption)selectedValue1);
if (selectedValue2 is not null && index != 2)
selected.Add((DropdownOption)selectedValue2);
if (selectedValue3 is not null && index != 3)
selected.Add((DropdownOption)selectedValue3);
return test.Except(selected);
}
private List<DropdownOption> test = new List<DropdownOption>()
{
new("BOB", "BOB"),
new("GEOFF", "GEOFF"),
new("GREGROY", "GREGORY"),
new("GERTRUDE", "GERTRUDE"),
new("NameyMcNameFace", "NameyMcNameFace"),
new("GILES", "GILES")
};
}

Dropdown menu from SELECT distinct

I am trying to create a drop-down menu from distinct values.
SELECT DISTINCT RoleGroup
FROM ccf.role
In my Controller I am
var RoleGroups = db.Roles.Select(x => x.RoleGroup).Distinct();
ViewBag.RoleGroups = new SelectList(RoleGroups, "RoleGroup", "RoleGroup", null);
In my View I am
#Html.DropDownListFor(model => model.RoleGroup,
(#ViewBag.RoleGroups) as IEnumerable<SelectListItem>,
new { htmlAttributes = new { #class = "form-control" } })
I get an error of
What is all this?
Your query is returning IEnumerable<string> and your SelectList constructor is trying to access the RoleGroup property of string (the 2nd and 3rd parameters) which does not exist. It needs to be
ViewBag.RoleGroups = new SelectList(RoleGroups);

Drop-down not working in ToSelectList extension method

I've got a nullable enum that, unlike others on the same page, doesn't work. I have an enum, Title whereby using the extension method will help to populate a drop-down list on the page. Here's what the ViewBag declaration looks like:
ViewBag.TitleList = EnumExtensions.ToSelectList<Title>("[select]");
Now, perhaps someone could explain it to me, but this is where the black magic happens when it comes to binding in MVC. If the page is invalid when calling if(ModelState.IsValid) then upon re-rendering the screen, the above statement is called again. However this time, the correct drop-down item will be selected (dependent on which one you had selected at the time).
Digging deeper, this is the method declarations:
public static SelectList ToSelectList<TEnum>(string nullEntry = null) where TEnum : struct
{
return ToSelectList<TEnum>(nullEntry, null);
}
public static SelectList ToSelectList<TEnum>(string nullEntry = null, string selectedValue = null) where TEnum : struct
{
var enumType = typeof(TEnum);
var values = Enum.GetValues(enumType).OfType<TEnum>();
List<SelectListItem> items = ToSelectList<TEnum>(values, nullEntry, selectedValue);
SelectList sl = new SelectList(items, "Value", "Text", selectedValue);
return sl;
}
public static List<SelectListItem> ToSelectList<T>(this IEnumerable<T> enumerable, string nullEntry, string selectedValue = null)
{
List<SelectListItem> items;
if ((typeof(T).IsEnum))
{
items = enumerable.Select(f => new SelectListItem()
{
Text = f.GetDescription(),
Value = f.ToString(),
Selected = f.ToString() == selectedValue
}).ToList();
}
else
{
items = enumerable.Select(f => new SelectListItem()
{
Text = f.ToString(),
Value = f.ToString()
}).ToList();
}
if (!string.IsNullOrEmpty(nullEntry))
{
items.Insert(0, new SelectListItem() { Text = nullEntry, Value = "" });
}
return items;
}
There's just some overloads to handle random cases, although presumably some of these won't be needed.
As I say, the correct item will be selected for other enumerations, but for this particular one, Title, it will not. Here's the enum declaration:
public enum Title
{
Mr,
Miss,
Mrs,
Ms
}
And finally, the declaration using DropDownListFor on the page itself;
#Html.DropDownListFor(x => x.Title, (SelectList)ViewBag.TitleList)
The problem is that when I first visit the page, the selected item is always "[select]" (when the provided enum value is null in the model). However, the model property Title definitely has a value set, and the SelectedItem property is set for the drop-down list too, but on screen, it defaults to "[select]" which is unexpected.
Any ideas?
Could it be because of the name Title? Try changing it to another name just to see.
Maybe you should try adding String.Empty so it the drop down list will default to a blank
#Html.DropDownListFor(x => x.Title, (SelectList)ViewBag.TitleList, String.Empty)

MVC3 DropDownListFor not populating selectedValue

I feel like I'm taking crazy pills. I have a dropdownlist for a view that reads from our database all of the wine producers we have. I want to set the selectedValue to a particular ID driven by the referring page. I can see it picks up the selectedValue in debug, I see the selected value populated (906 for this example), but it doesn't set the dropdownlist to the correct value when the page is rendered, it always defaults to 1 for the default value. I've tried creating the selectList in razor as opposed to my controller, but nothing works. Any help on this would be appreciated, I'm guessing it is something small.
Controller:
if (User.IsInRole("admin"))
{
if (ID != 0)
{
ViewBag.ProducerSelect = new SelectList(db.Producers.OrderBy(p => p.Name), "ProducerID", "Name", ID);
}
else
{
ViewBag.ProducerSelect = new SelectList(db.Producers.OrderBy(p => p.Name), "ProducerID", "Name");
}
}
View:
if (User.IsInRole("producereditor"))
{
<h3>#ViewBag.ProducerName</h3>
}
else
{
<div class="editor-label">
#Html.LabelFor(m => m.Wine.ProducerID, "Producer")
</div>
<div class="editor-field">
#Html.DropDownListFor(m => m.Wine.ProducerID, ViewBag.ProducerSelect as SelectList)
</div>
}
Tried the below but no success:
ViewBag.ProducerSelect = new SelectList(from p in db.Producers
orderby p.Name
select new { p.ProducerID, p.Name }
, "ProducerID", "Name", ID);
If you want to preselect an item, You set that value to your ProducerId property.
var yourViewModelObj=new YourViewModel;
yourViewModelObj.Wine.ProducerId=906; //or whatever value you want
return View(yourViewModelObj);
Suggestion : For better code readablity/Maintenance, Try to avoid ViewBag / ViewData and use a ViewModel to pass the data.
I would add a Property to my ViewModel to hold the Collection of Producers
public class WineViewModel
{
//Other Existing properties also
public IEnumerable<SelectListItem> Producers{ get; set; }
public string SelectedProducer { get; set; }
}
Then in yout GetAction method, you can set the value like this, If you want to set one select option as the default selected one.
public ActionResult CreateWine()
{
var vm=new WineViewModel();
//The below code is hardcoded for demo. you mat replace with DB data.
vm.Producers= new[]
{
new SelectListItem { Value = "1", Text = "Prodcer A" },
new SelectListItem { Value = "2", Text = "Prodcer B" },
new SelectListItem { Value = "3", Text = "Prodcer C" }
};
//Now let's set the default one's value
vm.SelectedProducer = "2";
return View(vm);
}
And in your Strongly typed View,
#Html.DropDownListFor(x => x.SelectedProducer,
new SelectList(Model.Producers, "Value", "Text"), "Select Producer")
The HTML Markup generated by above code will have the HTML select with the option with value 2 as selected one.
I figured this out. I had ViewModel.wine.ProducerID elsewhere on the page in a hidden field, and that defaults to 1, so I just assigned that to passed in value, and it worked great. I knew it was something like that. Thanks!
User a ViewModel ex WineViewModel
public class WineViewModel
{
public Wine Wine { get; set; }
public SelectList PProducerList { get; set; }
public WineViewModel() { }
public WineViewModel(Wine wine)
{
this.Wine = wine;
}
}
Try the following in your controller
var model = new WineViewModel( selectwine);
model.ProjectTypeList = new SelectList( from p in db.Producers
orderby p.Name
select new { p.ID, p.Name }, "ID", "Name")
notice how I am exclusively declaring which is the ID and which is the Value in my SelectList
Then in your view do
#Html.DropDownListFor(model => model.Wine.ProducerID, Model.ProjectTypeList)

How do you properly create a MultiSelect <select> using the DropdownList helper?

(sorry, there are several item here but none seems to allow me to get this working.)
I want to create a DropDownList which allows multiple selection. I am able to populate the list but I can't get the currently selected values to seem to work.
I have the following in my controller:
ViewBag.PropertyGroups = from g in db.eFinGroups
where g.GroupType.Contents == "P"
select new
{
Key = g.Key,
Value = g.Description,
Selected = true
};
ViewBag.SelectedPropertyGroups = from g in company.Entities
.First().Properties.First().PropertyGroups
select new {
g.eFinGroup.Key,
Value = g.eFinGroup.Description };
In the view I have:
#Html.DropDownListFor(model => model.PropertyGroupsX,
new MultiSelectList(ViewBag.PropertyGroups
, "Key", "Value"
, ViewBag.SelectedPropertyGroups),
new { #class = "chzn-select", data_placeholder = "Choose a Property Group", multiple = "multiple", style = "width:350px;" })
PropertyGroupX is a string[] in the model.
I have tried all types of iterations with the selected properties... passing just the value, just the key, both, etc.
Also, what type is PropertyGroupX supposed to be? Is string array correct? Or should it be a dictionary that contains the current propertygroups? I really am having a hard time finding doc on this.
Someone suggested I should be using ListBoxFor. I have changed to that and still have the same issue. The selected values are not being set as selected when the option tags are rendered. Here is what I have tried:
#Html.ListBoxFor(model => model.PropertyGroups, new MultiSelectList(ViewBag.PropertyGroups, "Key", "Value"))
I have tried the model.PropertyGroups as a collection of string matching the Values, as a collection of Guid matching this IDs and as an anonymous type with both a Key and Value to match the items in the ViewBag. Nothing seems to work.
You don't use DropDownListFor if you want to create a multiselect list. You use the ListBoxFor helper.
View model:
public class MyViewModel
{
public string[] SelectedIds { get; set; }
public IEnumerable<SelectListItem> Items { get; set; }
}
Controller:
public ActionResult Index()
{
var model = new MyViewModel
{
// preselect the first and the third item given their ids
SelectedIds = new[] { "1", "3" },
// fetch the items from some data source
Items = Enumerable.Range(1, 5).Select(x => new SelectListItem
{
Value = x.ToString(),
Text = "item " + x
})
};
return View(model);
}
View:
#model MyViewModel
#Html.ListBoxFor(x => x.SelectedIds, Model.Items)

Resources