public class State
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class Address
{
public State State { get; set; }
}
public class JobSeeker
{
public Address CurrentAddress { get; set; }
}
public class RegisterVM
{
public JobSeeker JobSeeker { get; set; }
public List<State> AllStates { get; set; }
}
in Razor
#Html.DropDownListFor(m => m.JobSeeker.CurrentAddress.State,
new SelectList(Model.AllStates, "Id", "Name" ), " -----Select List----- ")
The result is the drop down is populated with the value present in AllStates, but the problem is m.JobSeeker.CurrentAddress.State is null when posted to controller action. How to set the selected value of dropdown to property m.JobSeeker.CurrentAddress.State
If you change the ViewModel to...
public class RegisterVM
{
public JobSeeker JobSeeker { get; set; }
public List<State> AllStates { get; set; }
public string SelectedState { get; set; }
}
..and have the Drop down use the SelectedState property instead...
#Html.DropDownListFor(m => m.SelectedState,
new SelectList(Model.AllStates, "Id", "Name" ), " -----Select List----- ")
You should then be able to assign it to the State by name.
How is the model binder supposed to map an Id of the state to the model of type State??? If you change your razor view code to:
#Html.DropDownListFor(m => m.JobSeeker.CurrentAddress.State.Id, new SelectList(Model.AllStates, "Id", "Name" ), "--Select--")
Then you will get a non-null instance of state but only the Id property will be populated...
Thanks all of you. I was able to figure out the problem after viewing the Request.Form object which has only one value against State Field i.e SelectedValue of dropdownlist.
As far I understand, it is not possible to set the state property from UI hence I have to use the selected ID of State received from view in the ModelBinder or Controller to set the State Object from db .
Related
I have class include the following objects
public class ProductViewer
{
public J_Items Item { get; set; }
public List<J_ItemsImages> lstItemImages { get; set; }
}
how can i add DropDownListFor Item.ItemName
J_Items definition
public int ID { get; set; }
public string ItemName { get; set; }
public string ItemShortDesc { get; set; }
public string ItemLongDesc { get; set; }
#Html.DropdownlistFor(m => m.Item.ItemName)
Where m is your ProductViewer class, of course that should be made in to a viewmodel.
You can use the first argument of the Html.DropDownListFor() helper method to designate which property you want to bind to and then use the second to supply a collection to populate the actual drop down list with :
<!-- This would bind to the ItemName property, but would populate the list with
the contents of your lstItemImages property -->
#Html.DropDownListFor(m => m.Item.Name, new SelectList(Model.lstItemImages, "Name", "Name"))
Likewise, if you had a collection of values on your J_Items class, you could access those in the second parameter as well:
#Html.DropDownListFOr(m => m.Item.Name, new SelectList(Model.Item.SomeCollection,"Value,"Text")
This assumes that your parent ProductViewer class would be passed into the view.
I have an MVC application. Here is a part of my code:
View Model:
public class StudentViewModel
{
public int Id { get; set; }
public int? DepartmentId { get; set; }
public string Name { get; set; }
public string Department { get; set; }
public IEnumerable<SelectListItem> AllDepartments { get; set; }
}
View:
#Html.DropDownListFor(model => model.DepartmentId, Model.AllDepartments)
Controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(StudentViewModel studentViewModel)
{
After the user changes the department selection in the dropdown and submits, for some reason the studentViewModel.DepartmentId contains the old DepartmentId. studentViewModel.Name does contain the new value. What am I missing?
From the comments, you have included another control for property DepartmentId before the dropdownlist. The DefaultModelBinder reads all form values in order (plus route values, query string values etc). Once a property value has been set, any subsequent values for the same property are ignored.
I try to add a new Country which has a link to continent. When I press the "Create" button, it doesn't add a new record. I debugged my project and I think it's because the ValidState is false. The reason because of this is that the property "Continent" is null, but the Continent_Id isn't.
I have the same problem when I try to edit an existing Country. (I have populated my database with an SQL script in SQL Management Studio)
Can someone help me please?
Continent class:
public class Continent
{
public int Id { get; set; }
[Required, MaxLength(25)]
public string Name { get; set; }
//Navigation
public virtual List<Country> Countries { get; set; }
}
Country class
public class Country
{
public int Id { get; set; }
[Required, MaxLength(25)]
public string Name { get; set; }
[MaxLength(5)]
public string Abbreviation { get; set; }
public int Continent_Id { get; set; }
//Navigation
[Required, ForeignKey("Continent_Id")]
public virtual Continent Continent { get; set; }
}
Controller class ( create function )
//
// GET: /Countries/Create
public ActionResult Create()
{
ViewBag.Continent_Id = new SelectList(db.Continents, "Id", "Name");
return View();
}
//
// POST: /Countries/Create
[HttpPost]
public ActionResult Create(Country country)
{
var errors = ModelState.Values.SelectMany(v => v.Errors); //to check the errors
if (ModelState.IsValid)
{
db.Countries.Add(country);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.Continent_Id = new SelectList(db.Continents, "Id", "Name", country.Continent_Id);
return View(country);
Just before the line if (ModelState.IsValid) put this
ModelState.Remove("v_id");
Where v_id is your primarykey column name in your case
I fixed this issue by putting the Required validation off of Continent, and set it only at the Continent_Id. Now the ID property is required, but the Continent isn't.
public class Country
{
public int Id { get; set; }
[Required, MaxLength(25)]
public string Name { get; set; }
[MaxLength(5)]
public string Abbreviation { get; set; }
[Required] //added Required
public int Continent_Id { get; set; }
//Navigation
[ForeignKey("Continent_Id")] //removed Required
public virtual Continent Continent { get; set; }
}
Thanks for the responses !
I'm not sure, but I believe your issue is timing. Model validation happens automatically during binding; at that time, the Continent property is null. You set the property later but the model state is not re-evaluated when you check IsValid. I see three options:
Quick and dirty: Take the Required validation off of Continent and validate Continent_Id instead, adding a check in the controller to ensure a valid Continent is retrieved from Find().
Most work: Create a custom model binder to actually use the Continent_Id to retrieve and populate the Continent. You are almost there on this one since having both Continent_Id and Continent as properties of Country is redundant and an opportunity for inconsistencies.
Probably best option: Make your controller accept a view model that only has the data you expect to come back from the form and populate a Country object from it.
The reason the ModelState isn't valid is because you have marked the Continent property as required but in i guess in your view you don't have form fields the will bind to some properties of the Continent object.
So either don't mark the Continent object as required or provide a hidden field with a name of Continent.Id or Continent.Name so that the model binder will populate the Continent property:
#Html.HiddenFor(m => m.Continent.Id)
But that will lead to the next problem: You habe marked the Name property of the Continent class as required so you will have to provide a form field for that property too.
The base problem is, that you try to reuse your repository classes as viewmodel classes.
A better approach would be to use separate classes as viewmodels to pass your data between the controller and the view:
class CountryViewModel {
public int Id { get; set; }
[Required, MaxLength(25)]
public string Name { get; set; }
[MaxLength(5)]
public string Abbreviation { get; set; }
public int Continent_Id { get; set; }
}
To map between your Country and CountryViewModel object use a mapper like AutoMapper.
I have two identical dropdownlists for identical subproperties of my item TaskViewModel.
#Html.DropDownListFor(x => x.AssignedTo.ID, TaskHelper.GetUsersSelect(Model.AssignedTo), new { #class = "select" })
#Html.DropDownListFor(x => x.Controller.ID, TaskHelper.GetUsersSelect(Model.Controller), new { #class = "select" })
Both values sending on posting form (...&AssignedTo.ID=1&Controller.ID=1...), but I always get only task.AssignedTo deserialized
public ActionResult SaveTask(TaskViewModel task)
{
task.AssignedTo.ID //value here
task.Controller.ID //null reference exception
}
public class TaskViewModel
{
public UserViewModel Controller { get; set; }
public UserViewModel AssignedTo{ get; set; }
}
public class UserViewModel
{
public long ID { get; set; }
public string Title { get; set; }
}
What may be a reason for that strange behaviour?
It looks like Controller is a reserved keyword and the default model binder doesn't like it. Try renaming the property:
public class TaskViewModel
{
public UserViewModel TaskController { get; set; }
public UserViewModel AssignedTo { get; set; }
}
The whole problem comes from the ValueProvider that the default model binder uses. When it encounters the Controller navigation property it does this:
var value = ValueProvider.GetValue("Controller");
which unfortunately first looks at route data and after that in query string. So this returns "Home" or whatever the name of the controller is and obviously trying to assign the string "Home" to a class of type UserViewModel cannot result in success.
I get the following error in the DropDownListFor() "An expression tree may not contain a dynamic operation" because the lambda uses a dynamic type.
How can I set the selected option on the DropDownList without resorting to jQuery?
I would also rather not make a template or custom helper.
Model
public class Thing : Base {
public virtual Nullable<int> OptionID { get; set; }
public virtual Option Option { get; set; }
}
public class Option : Base {
public virtual ICollection<Thing> Things { get; set; }
}
public class Base {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
Controller
public ActionResult Edit(int Id) {
return View(new ViewModel(context, new Thing()));
}
View
#model MvcApp7.Models.ViewModel
#{
var Entity = (dynamic)Model.Entity;
}
#Html.DropDownListFor(x => Entity.OptionID , (System.Web.Mvc.SelectList)Model.Options)
ViewModel
public class ViewModel {
public ViewModel(Context context, object entity) {
this.Entity = entity;
this.Options = new SelectList(context.Options, "Id", "Name");
}
public dynamic Entity { get; set; }
public SelectList Options { get; set; }
}
EDIT: Please excuse me. I forgot that I could specify the selected option in the SelectList itself. I moved the responsibility into the ViewModel and will try to deal with it from there. However, it would still be good to know how to work around this in the View itself in case it was necessary.
I did this in the ViewModel
this.Options = new SelectList(context.Options, "Id", "Name", this.Entity.OptionID);
and this in the View
#Html.DropDownList("OptionID", Model.Options)
View
#model MvcApp7.Models.ViewModel
#Html.DropDownListFor(x => x.Entity.OptionID , (System.Web.Mvc.SelectList)Model.Options)
ViewModel
public class ViewModel {
public ViewModel(Context context, object entity) {
this.Entity = entity;
this.Options = new SelectList(context.Options, "Id", "Name");
}
public dynamic Entity { get; set; }
public SelectList Options { get; set; }
}