DropDownList not getting selected - MVC4 - asp.net-mvc

I have the DropDownList in view as below,
#Html.DropDownListFor(model => model.RoleID, (IEnumerable<SelectListItem>)ViewBag.RoleID, new { #class = "dropdownlistCustom" })
#Html.DropDownList("RoleID", (IEnumerable<SelectListItem>)ViewBag.RoleID, new { #class = "dropdownlistCustom" })
#Html.ValidationMessageFor(model => model.RoleID)
Tried both DropDownList and DropDownListFor, both are not getting the correct SelectedValue.
From Controller, I am setting the selectedValue param (see Last parameter of SelectList()) .
public ActionResult Edit(int id = 0)
{
UserDetail userDetail=db.UserDetails.Find(id);
if(userDetail!=null)
{
ViewBag.RoleID = new SelectList(db.Roles.Where(r => r.RoleStatus == "A"), "RoleID", "RoleName", userdetail.RoleID);
return View(userdetail);
}
}
Model :
[Display(Name = "Name Of the Role")]
public int RoleID { get; set; }
[ForeignKey("RoleID")]
public virtual Role Roles { get; set; }

First of all I would encourage you not to use ViewBag, but view models to pass your select list (or data for select list) along from controllers to views.
Second of all, I would encourage you to look at this example or this example (preferably both) of DropDownListFor usage. I think you over-complicating SelectList. Less simple it is, less confused you will be as to why is it not working as expected. Just take existing example and change variable names to yours.
Another big tip why not to use ViewBag is that when you build your view model you can build a select list right there in the controller where you fetch your data from wherever. Then in the view you will only need to use pre-built view model property where SelectList argument is provided - cleaner and nicer. View should do least manipulations with data, but only to present it in the right format in the right way in the right place.
Just look at those examples I provided, I am sure you smart enough to figure it out how to use DropDownListFor properly, its very very easy, you just need to provide right parameters at the right places, see what others do and do the same.
Hope this helps, please keep me posted about your progress.

That's because you're not using DropDownListFor correctly. The function definition you're after is:
DropDownListFor(lambda, selectList, *selectedItem*, htmlAttributes)
RoleId should be passed as the selectedItem parameter, so you're missing the selectList parameter. MVC doesn't create that for you automatically with DropDownListFor, you have to tell it what select list to use by either creating it inline, passing it into ViewBag or setting it on your model and specifying which property to use.
EDIT
Sorry. That's my bad. It's still early here and we lost an hour (daylight savings), so my brain is not all there yet. I mixed DropDownListFor and DropDownList. With DropDownListFor, the selected item comes from the set value on the model, but you still need to provide a proper SelectList. It appears you're trying to specify the selected item where you should be telling it what SelectList to use.
If you were to use DropDownList, then you would need to specify both the SelectList to use and the selected value.

Related

How to make DropDownListFor use item IDs instead of indices?

I have a table with a dropdown list in each row, like this:
#Html.DropDownListFor(m => m.Transactions[i].CategoryID, ...)
and everything mostly works. I can select items, submit the form, and the model has my updated selections. So far so good.
The problem is that this isn't very reliable. Since the name of each dropdown is based on an index rather than an ID that means the match-up between the post values and the actual items living in the database are based on indices. Most of the time that works fine, but what if the list of items changes between the time the page loads and the time a user does a postback? The indices have changed, which means the post data won't match up correctly, and bad things happen. OR I've seen the browser incorrectly try to preserve selections in dropdowns between posts, but because the list of items is changing (what may be item #2 now may be item #3 by the time the page is refreshed) and everything is based on indices, the wrong dropdowns get the wrong values.
So basically, how can I force the dropdowns to generate a name and ID that looks more like this:
Transactions_CategoryID_12385652 // 12385652 is the CategoryID
rather than this:
Transactions_4_CategoryID // 4 is the array index
and still have the benefits of automatic binding?
Edit: The second issue I mentioned (input values being restored incorrectly after a refresh) seems to only happen with Firefox. https://bugzilla.mozilla.org/show_bug.cgi?id=46845
You'd have to write your own custom model binder as well as a Html extension to generate the element names in this way, or you could generate the markup manually using Razor.
Is there a particular reason you want need to do it this way? You're almost always following the conventions of the framework unless there's a good reason not to.
You can pass the exact collection you want to the view and bind that to the DropDownFor html helper.
Say you have a Person.
public class Person
{
public int Id { get; set; }
public int Name { get; set; }
}
And you want to add a new person. Create a view model. Create a property in this view model of type SelectList. This select list will hold the collection of the model you want to populate the dropdown list with.
public class PersonViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public SelectList MySelectList { get; set; }
}
Let's say you want the drop down list to hold the Person's Id as the selected value and the Person's name as the text value. You may want to create a second view model to represent that or use an anonymous object. Let's call the collection myCollection. And let's say that the collection is made up of objects that have two properties (and Id and Name property). Now simply pass this view model with a value for MySelectList.
var viewModel = new MyViewModel
{
MySelectList = new SelectList(myCollection, "Id", "Name")
};
In your razor view you can set up the #Html.DropDownListFor like so:
#Html.DropDownListFor(m => m.Id, Model.MySelectList)
In order to pass a select value to the view for the dropdownlist simply use the SelectList constructor overload that allows this value to be passed:
new SelectList(myCollection, "Id", "Name", selectedValue)

Why is DropDownListFor not recognizing the selected value in my editor template?

I have the following editor template called 'DropDown.cshtml'. The list part works fine, and the template uses some voodoo I did to get the required SelectList from ViewData. The controller places all select lists in the view model into ViewData, and there is nothing wrong with the list side of things.
#{
var list = this.GetModelSelectList();
}
#Html.DropDownListFor(m => Model, list)
I use this template on foreign key view model properties like this one:
[Required]
[UIHint("DropDown", "MVC", "SelectListName", "JobLevelSelectList")]
[Display(Name = "Job Level")]
public Guid? JobLevelId { get; set; }
public SelectList JobLevelSelectList { get; set; }
In the controller, JobLevelId has the correct value immediately before executing the view, yet no item it selected in the rendered select element. or rather, the first item in the select list is always selected.
Why does DropDownListFor ignore the property value when used in my editor template and yet work fine when invoked directly?
This is unfortunately a known bug in MVC3 (I haven't tried it in MVC 4 Beta to see if it fixed).
The work around that I have used is to manually set the Selected property accordingly in collection that the DropDownListFor is bound to, it is not ideal but it worked.

Can you remove the HTML Field Prefix from strongly typed models in MVC 3?

I have a view model like this:
public class EditVM
{
public Media.Domain.Entities.Movie Movie { get; set; }
public IEnumerable<Genre> Genres { get; set; }
}
Movie is the real entity I wish to edit. Genres is simply present to populate a drop down. I would prefer that when I call:
#Html.TextBoxFor(m => m.Movie.Title)
inside my strongly typed view that the input control have a name = "Title" instead of "Movie.Title"
I do not wish to split my view into partial views or lose my strongly typed view by using ViewData or the like.
Is there a way to express to the View that I do not wish to have the Movie. prefix? I noticed that you can set:
ViewData.TemplateInfo.HtmlFieldPrefix = "x";
in the controller, but unfortunately it seems only to allow adding an additional prefix. Setting it to "" does nothing.
Is there any work around for this? Or am I stuck with the unfortunate prefix that isn't really necessary in this case if I wish to keep strongly typed views and lambdas?
Thanks for any help.
Update:
Here's the controller actions to maybe make things a bit clearer.
public ActionResult Edit(int? id)
{
var vm = new EditVM
{
Movie = id.HasValue ? _movieSvc.Find(id.Value) : new Movie(),
Genres = AppData.ListGenres()
};
return View(vm);
}
[HttpPost]
public void Edit([Bind(Prefix = "Movie")]Movie m)
{
_movieSvc.AddOrUpdateMovie(m); //Exceptions handled elsewhere
}
No, in order to do what you want you would have to rewrite the Html helpers, and then you would have to write your own model binder. Seems like a lot of work for minimal gain.
The only choice is a Partial view in which you pass the Movie object as the model. However, this would require you to write your own model binder to have it be recognized.
The reason you have to do m.Movie.Title is so that the ID has the correct name, so the model binder can recognize it as a member of your model.
Based on your update:
Your options are:
Use non-strongly typed helpers.
Use a partial view.
Rewrite the stronly typed helpers
Don't use the helpers at all, and write the values to the HTML
Personally, i'd just use 1 or 2, probably 2.
EDIT:
Based on your update above. Change your code to this (note, Genres does not get posted back to the server, so m.Genres will just be null on postback):
[HttpPost]
public void Edit(EditVM m)
{
_movieSvc.AddOrUpdateMovie(m.Movie); //Exceptions handled elsewhere
}
EDIT:
I did just think of an alternative to this. You could simply do this:
#{ var Movie = Model.Movie; }
#Html.TextBoxFor(m => Movie.Title)
However, if there was a validation error, you would have to recreate your EditVM.
I have a view model like this
I think that you might have some misunderstanding about what a view model is. A view model shouldn't contain any reference to your domain models which is what those Movie and Genre classes seem to be. I mean creating a new class that you suffix with VM and in which you stuff all your domain models as properties is not really a view model. A view model is a class that is specifically designed to meet the requirements of your view.
A much more correct view model would looks like this:
public class EditVM
{
public string MovieTitle { get; set; }
public IEnumerable<GenreViewModel> Genres { get; set; }
}
and in your view you would have:
#Html.EditorFor(x => x.MovieTitle)
#Html.EditorFor(x => x.Genres)
Another option is to either use the TextBox(string name, object value) overload instead of the TextBoxFor:
#Html.TextBox("Title", Model.Movie.Title)
You could also specify the input tag HTML instead of using a helper.
Another option is to take EditVM as your postback parameter. This is what I would do. My post action parameter is always the same type of the .cshtml model. Yes there will be properties like lists that are null, but you just ignore those. It also allows you to gracefully handle post errors as well because if there is an error you'll need to return an instance of that view model anyhow, and have the values they submitted included. I usually have private methods or DB layer that handles retrieving the various lists that go into the ViewModel, since those will be empty on postback and will need to be repopulated, while not touching the properties that were in the post.
With your post method as it is now, if you need to return the same view, you've gotta create a new EditVM and then copy any posted values into it, and still populate the lists. With my method, you eliminate one of those mapping steps. If you are posting more than one thing, are you going to have umpteen different parameters on your post action? Just let them all come naturally into a single parameter typed to the EditVM of the View. While maybe having those null properties in the VM during the postback feels icky, you get a nice predictable consistency between View and postback IMO. You don't have to spend alot of time thinking about what combination of parameters on your post method will get you all the pieces of data from the form.

Why is ListBoxFor not selecting items, but ListBox is?

I have the following code in my view:
<%= Html.ListBoxFor(c => c.Project.Categories,
new MultiSelectList(Model.Categories, "Id", "Name", new List<int> { 1, 2 }))%>
<%= Html.ListBox("MultiSelectList",
new MultiSelectList(Model.Categories, "Id", "Name", new List<int> { 1, 2 }))%>
The only difference is that the first helper is strongly typed (ListBoxFor), and it fails to show the selected items (1,2), even though the items appear in the list, etc. The simpler ListBox is working as expected.
I'm obviously missing something here. I can use the second approach, but this is really bugging me and I'd like to figure it out.
For reference, my model is:
public class ProjectEditModel
{
public Project Project { get; set; }
public IEnumerable<Project> Projects { get; set; }
public IEnumerable<Client> Clients { get; set; }
public IEnumerable<Category> Categories { get; set; }
public IEnumerable<Tag> Tags { get; set; }
public ProjectSlide SelectedSlide { get; set; }
}
Update
I just changed the ListBox name to Project.Categories (matching my model) and it now FAILS to select the item.
<%= Html.ListBox("Project.Categories",
new MultiSelectList(Model.Categories, "Id", "Name", new List<int> { 1, 2 }))%>
I'm obviously not understanding the magic that is happening here.
Update 2
Ok, this is purely naming, for example, this works...
<%= Html.ListBox("Project_Tags",
new MultiSelectList(Model.Tags, "Id", "Name", Model.Project.Tags.Select(t => t.Id)))%>
...because the field name is Project_Tags, not Project.Tags, in fact, anything other than Tags or Project.Tags will work. I don't get why this would cause a problem (other than that it matches the entity name), and I'm not good enough at this to be able to dig in and find out.
I've stumbled across this problem myself, finally I realized that the problem was a naming convention.
You cannot name the ViewBag or ViewData poperty containing the SelectList or MultiSelectList to the same name your property model containing the selected items. At least not if you're using the ListBoxFor or DropDownListFor helper.
Here's an example:
public class Person
{
public List<int> Cars { get; set; }
}
[HttpGet]
public ActionResult Create()
{
//wont work
ViewBag.Cars = new SelectList(carsList, "CarId", "Name");
//will work due to different name than the property.
ViewBag.CarsList = new SelectList(carsList, "CarId", "Name");
return View();
}
//View
#Html.ListBoxFor(model => model.Cars, ViewBag.CarsList as SelectList)
I'm sure theres plenty of other ways doing this, but it solved my problem, hope it will help someone!
I have also been stuck with this exact same issue and encountered the same problem with ListBox and ListBoxFor.
No matter what I do, I cannot get selections to occur on the ListBoxFor. If I change to the ListBox and name it something OTHER than the property name of the data I am binding to, selections occur.
But then because I'm not using ListBoxFor and the data is sitting inside a model class (Model.Departments) for example, I don't get model binding on the way back to my controller and hence the property is null.
EDIT I found a solution posted by someone else here;
Challenges with selecting values in ListBoxFor
Also, you can try to clear ModelState for c.Project.Categories in the controller:
[HttpPost]
public ActionResult Index(ModelType model)
{
ModelState.Remove("Project.Categories");
return View("Index", model);
}
And use the next construction:
<%= Html.ListBoxFor(c => c.Project.Categories,
new MultiSelectList(Model.Categories, "Id", "Name"))%>
Where c.Project.Categories is IEnumerable<int>.
Sorry for my english. Good luck!
The correct answer is that it doesn't work very well. As such I read the MVC code. What you need to do is implement IConvertible and also create a TypeConverter.
So, in my instance I had a Country class, such that people could choose from a list of countries. No joy in selecting it. I was expecting an object equals comparison on the selectedItems against the listitems but no, that's not how it works. Despite the fact that MultiListItem works and correctly gets the selected items, the moment it is bound to your model it's all based on checking that the string represnetation of your object instance matches the string "value" (or name if that is missing) in the list of items in the SelectItemList.
So, implement IConvertible, return the string value from ToString which would match the value in the SelectItemList. e.g in my case CountryCode was serialized into the SelectItem Value property , so in ToString IConvertible I returned CountryCode. Now it all selects correctly.
I will point out the TypeConverter is used on the way in. This time its the inverse. That Countrycode comes back in and "EN" needs converting into Country class instance. That's where the TypeConverter came in. It's also about the time I realised how difficult this approach is to use.
p.s so on your Category class you need to implement IConvertible. If its from the entity framework as my company is then you'll need to use the partial class to implement IConvertible and implement ToString and decorate it with a TypeConverter you wrote too.
Although this isn't an answer to your main question, it is worth noting that when MVC generates names it will turn something like Project.Tags into Project_Tags, replacing periods with underscores.
The reason that it does this is because a period in an element ID would look like an element named Project with a class of Tags to CSS. Clearly a bad thing, hence the translation to underscores to keep behaviour predictable.
In your first example,
<%= Html.ListBoxFor(c => c.Project.Categories,
new MultiSelectList(Model.Categories, "Id", "Name", new List<int> { 1, 2 }))%>
the listbox is attempting to bind to Model.Project.Categories for your strongly typed Model which has been provided to the page (using the lambda notation). I'm not sure what the second parameter in the ListBoxFor is doing though.
What is the Model that is being passed to the page?
Try this
<%= Html.ListBoxFor(c => c.Project.Categories,
new MultiSelectList(
Model.Categories
,"Id"
,"Name"
,Model.Project.Tags.Select(
x => new SelectListItem()
{
Selected = true,
Text = x.TEXT,
Value = x.ID.ToString()
}).ToList())
)
)%>
Html.ListboxFor and Html.Listbox work great when you're NOT binding the list box to its data source. I assume the intended use is this:
// Model
public class ListBoxEditModel
{
public IEnumerable<Category> Categories { get; set; }
public IEnumerable<Category> SelectedCategories { get; set; }
}
In the view:
#Html.ListBoxFor(m => m.SelectedCategories,
new MultiSelectList(Model.Categories, "Id", "Name"))
// or, equivalently
#Html.ListBox("SelectedCategories" ,
new MultiSelectList(Model.Categories, "Id", "Name"))
Note that in this case you don't have to explicitly say which values are selected in the MultiSelectList - the model you're binding to takes precedence, even if you do!

How do I set a value for the default option with Html.DropDownList

I'm using ASP MVC RC1.
A form I'm using contains a dropdownlist which I have put in a view with this code.
<%= Html.DropDownList("areaid", (SelectList)ViewData["AreaId"], "Select Area Id")%>
However, when rendered, this is what I get
<select id="areaid" name="areaid">
<option value="">Select Area Id</option>
<option value="1">Home</option>
...
</select>
What I'd like is for the Select Area Id option to have a value of 0 and mark it as selected by default so it is consistent with the other values and I can validate whether or not an area has been chosen as it is a mandatory value. AreaId is an integer so when I currently click the form without touching the dropdownlist at all, MVC complains that "" is not an integer and gives me a binding error.
SO how do I set a value for the default option and then make it selected on the form?
Thanks, Dan
I think you have three four options. First when you are building your SelectList or enumerable of SelectItemList, prepend the selection with your option label and default value. Putting it at the top will make it the default if some other value isn't already chosen in the model. Second, you could build the select (and options) "by hand" in the view using a loop to create the options. Again, prepending your default selection if one isn't supplied in the model. Third, use the DropDownList extension, but modify the value of the first option using javascript after the page is loaded.
It doesn't seem to be possible to use the DropDownList extension to assign a value to an optionLabel as it is hard-coded to use string.Empty. Below is the relevant code snippet from http://www.codeplex.com/aspnet.
// Make optionLabel the first item that gets rendered.
if (optionLabel != null) {
listItemBuilder.AppendLine(ListItemToOption(new SelectListItem() { Text = optionLabel, Value = String.Empty, Selected = false }));
}
EDIT: Finally, the best way is to have your model take a Nullable value and mark it as required using the RequiredAttribute. I would recommend using a view-specific model rather than an entity model for the view. Since the value is Nullable, the empty string will work fine if posted back without choosing a value. Setting it as a required value will cause the model validation to fail with an appropriate message that the value is required. This will allow you to use the DropdownList helper as is.
public AreaViewModel
{
[Required]
public int? AreaId { get; set; }
public IEnumerable<SelectListItem> Areas { get; set; }
...
}
#Html.DropDownListFor( model => model.AreaId, Model.Areas, "Select Area Id" )
For MVC3, SelectList has an overload whereby you can define the selected value.
Function Create() As ViewResult
ViewBag.EmployeeId = New SelectList(db.Employees, "Id", "Name", 1)
Return View()
End Function
In this case, I happen to know that 1 is the id of the default list item I want, but presumably you could select the default via query or what ever floats your boat
Instead of passing the default item from the definition in the View, You can add the "Select Area" data at the 0th index of the List from the controller.
This way the Select Area data has an index value of 0.
I wanted to use the same SelectList for multiple drop downs and didn't want to duplicate the SelectList in the model so I just added a new Html Extension method that took in a value and set the selected item.
public static MvcHtmlString DropDownList(this HtmlHelper htmlHelper, string name, string value, IList<SelectListItem> selectList, object htmlAttributes)
{
IEnumerable<SelectListItem> items = selectList.Select(s => new SelectListItem {Text = s.Text, Value = s.Value, Selected = s.Value == value});
return htmlHelper.DropDownList(name, items, null /* optionLabel */, htmlAttributes);
}

Resources