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!
Related
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)
I am using MVC3, Razor, C#, .NET4, EF5.
I have a number of situations where I am editing the domain model directly, and yes I realise that I should be using View Models :) However for the short term I am hoping I can get a solution to my problem whereby I am getting nulls being saved to the DB, where those fields are not specified as Hidden Fields in the forms. I realise this is to do with the Model Binding behaviour.
My Edit Action:
public ActionResult Edit(int id)
{
StdOrg myOrg = db.StdOrg.Single(s => s.Id == id);
return View(myOrg);
}
Lets assume my View has 5 fields on it:
Name
Address1
Address2
Address3
Address4
Now for my Post Edit Action:
[HttpPost]
public ActionResult Edit(StdOrg myOrg)
{
if (ModelState.IsValid)
{
db.StdOrg.Attach(myOrg);
db.ObjectStateManager.ChangeObjectState(myOrg, EntityState.Modified);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(myOrg);
}
Now my Table record has 10 columns in it, and columns 6-10 has values.
Name
Address1
Address2
Address3
Address4
Terms
EmployeeNo
Code
Active
SetUpDate
When I save the form, columns 6-10 ie Terms etc. get set to null. Now I realise this is due to the fact that I am not specifying them as hidden fields in my View and therefore MVC Model Binding is assuming these are null.
Is there a practical way around this, rather than specifying all columns in the form which I would rather not do for security reasons.
Many thanks in advance.
EDIT
My attempt which does not seem to work:
[HttpPost]
public ActionResult Edit([Bind(Include = "Id,Name,Name,Address1,Address2,Address3,Address4")] StdOrg myOrg)
Thoughts.... ???
IN CONCLUSION
For those interested I ended up using the "Value Injector" which is the other "Automapper" for which I wrote a simple matching routine to prevent nulls being set, and also to exclude navigation properties as well. Very useful Mapper.
One still really needs to implement ViewModels since these seem the only way to identify which of the classes properties are in the View, unless one can get Bind attribute working which I could not.
Otherwise even with a mapper one cannot reset a View field to null, since the mapper will ensure that it is ignored !! It cannot tell between View Properties and others in the domain class, as far as I can see.
Try using the BindAttribute, you can simply exclude or include the fields for that particular action (which is always good practice, someone could manipulate the ID field on the POST and modify a different value).
See this question for an example
Despite putting Exclude stuff:
[Bind(Exclude = "PropertyName,PropertyType")]
public class Filter {
public string PropertyName { get; set; }
public Type PropertyType { get; set; }
public string From { get; set; }
public string To { get; set; }
public string Match { get; set; }
}
those properties seem to be ovewritten with NULL, when I call this method from model (note: PropertyName and PropertyType are not and must not be present in markup! They are filled once in the code, before making markup)
[HttpPost]
public virtual PartialViewResult LoadFiltered(string entityType, IDictionary<string, Filter> filters)
And when no elements selected (all filters are disabled in interface) - I get two elements, having "controller" and "action" as keys. What the... ??
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.
I am creating a form with a drop down for a property named "Event". Unfortunately this is a reserved word so it won't work as usual:
#Html.DropDownListFor(x => x.Event, new SelectList(ViewData["Events"] as IEnumerable<JFS.Data.Model.Event>, "Id", "Name"))
In the controller I can't take this value separately as it is a reserved word, so if I do this:
public ActionResult Create(int event)
{
etc etc
}
It throws an error.
What I would ideally like to do is change the name of the dropdown list, something like this:
#Html.DropDownListFor(x => x.Event as eventId, new SelectList(ViewData["Events"] as IEnumerable<JFS.Data.Model.Event>, "Id", "Name"))
But that doesn't work. Anyone know of the correct way to change the name? :-)
You cannot change the name generated by HTML helpers and this is by design. What you could do instead is to change the name of your action parameter and prefix it with # which allows to use reserved words in C# as variable names:
public ActionResult Create(int #event)
{
etc etc
}
But in general it is not recommended to use reserved words as variable names unless absolutely necessary. And in your case it is not absolutely necessary because there's a much better solution which of course consists in using a view model:
public class CreateEventViewModel
{
public int Event { get; set; }
}
and then having your controller action take this view model as argument:
public ActionResult Create(CreateEventViewModel model)
{
etc etc
}
My domain object Store holds a reference to the object Chain:
public class Store
{
public Chain Chain { get; set; }
}
On creating a new store there is the possibility to set the associated chain. I am realising this by passing the chain objects via ViewBag and use the Html.DropDownListFor for selection:
#Html.DropDownListFor(
x => x.Chain,
new SelectList(ViewBag.Chains, "Id", "Name"))
So far so good, but back in the controller (after submitting) the chain property is null. I figured that I can set the DropDownList to Chain.Id but then I need to load the entity again. Is there a better way to get/keep the correct reference?
Is there a better way to get/keep the correct reference?
No, that's how HTML works. Only the selected value is sent to the server and you need to use this value to load the corresponding entity. Please learn HTML before getting into ASP.NET MVC development. It would help you very much. That's the correct way:
#Html.DropDownListFor(
x => x.Chain.Id,
new SelectList(ViewBag.Chains, "Id", "Name")
)
Well, as a matter of a real fact, to be totally honest with you the most correct way to handle this is to get rid of all ViewBag crap and use a real view model in your application:
#Html.DropDownListFor(
x => x.ChainId,
Model.AvailableChains
)
where AvailableChains will of course be a property of type IEnumerable<SelectListItem> on the view model you prepared for this view. You should always be using a view model and never be passing your domain models to your view if you want to be doing ASP.NET MVC the right way.
And here's how your view model might look like:
public class MyViewModel
{
public int ChainId { get; set; }
public IEnumerable<SelectListItem> AvailableChains { get; set; }
}