Cannot pass new selected dropdown value to the controller - asp.net-mvc

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.

Related

DropDownListFor not returning a Selected Value in Modified Scaffold Razor Page

I am rather new to Razor and I'm using the default templates. I have a ViewModel which contains three models:
public class CarAndOwnerViewModel
{
public IEnumerable<SelectListItem> Cars { get; set; }
public SelectListItem SelectedCar { get; set; }
public OwnerModel Person{ get; set; }
}
I am using this ViewModel in my create method. To get the list of Cars I use:
#Html.DropDownListFor(Model => Model.SeletedCar, new SelectList(Model.Cars, "Value", "Text", Model.Cars))
When I submit this drop down list the value of CarAndOwnerViewModel.SelectedCar is null. I Have a textbox that contains the following and it has it in CarAndOwnerViewModel.Owner.Name:
<input asp-for="Owner.Naame" class="form-control" />
What I filled out in that input box is passed on. I am not sure why the Input Boxes are working but the Select box isn't.
The issue was that the SelectedCar should be an int instead of a SelectedItem
Changing the class to
public class CarAndOwnerViewModel
{
public IEnumerable<SelectListItem> Cars { get; set; }
public int SelectedCar { get; set; }
public OwnerModel Person{ get; set; }
}
Fixed the issue.

MVC Multiple tables one model

I am having difficulty with my understanding of MVC coming from an aspx world.
I have a Model called CustomerGarment. This has a Order and a Customer along with a few garments.
public class CustomerGarment
{
public int CustomerGarmentId { get; set; }
public virtual Customer Customer { get; set; }
public virtual Order Order { get; set; }
public virtual GarmentJacket GarmentJacket { get; set; }
public virtual GarmentShirt GarmentShirt { get; set; }
}
I have a method for get and post. When the page loads, it creates a new CustomerGarment instance and querys the database to fill the Customer and Order variables. I then use the viewbag to show on the screen a list of GarmentJackets and GarmentShirts
The page then views and using the view I can access the model perfectly. Drop downs load with the viewbag contents and I can access all Customer and Order variables using the model I have passed.
The problem I then face is when I use the HttpPost. The model is not passed back with the information I passed to it.
public ActionResult AddGarments(int orderId, int customerId)
{
CustomerGarment cg = new CustomerGarment();
cg.Order = (from a in db.Orders where a.OrderId == orderId select a).FirstOrDefault();
cg.Customer = (from a in db.Customers where a.CustomerId == customerId select a).FirstOrDefault();
var jackets = from a in db.GarmentJackets orderby a.Type, a.SleeveLengthInches, a.ChestSizeInches select a;
var shirts= from a in db.GarmentKilts orderby a.PrimarySize, a.DropLength select a;
ViewBag.GarmentJacket = new SelectList(jackets, "GarmentJacketId", "GarmentJacketId");
ViewBag.GarmentShirt = new SelectList(shirts, "GarmentShirtId", "GarmentShirtId");
return View(cg);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddGarments(CustomerGarment cg)
{
// Here, I do not have the customer info for example
db.CustomerGarments.Add(cg);
db.SaveChanges();
return RedirectToAction("Index");
return View(cg);
}
This is a bit of my view
#Html.HiddenFor(model => model.Order.OrderId)
#Html.HiddenFor(model => model.Order.CustomerId)
<div class="display-field">
#Html.DisplayFor(model => model.Customer.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.GarmentJacket, "Jacket")
</div>
<div class="editor-field">
#Html.DropDownListFor(m => m.GarmentJacket, (SelectList)ViewBag.GarmentJacket, new {style="width:312px;height:30px;margin-top:2px;margin-bottom:5px"})
</div>
EDIT
My Garment Jacket Model
public class GarmentJacket : Garment
{
public int GarmentJacketId { get; set; }
[Required]
public string Type { get; set; }
[Required]
[Display(Name = "Chest Size")]
public int ChestSizeInches { get; set; }
[Required]
[Display(Name = "Sleeve Length")]
public int SleeveLengthInches { get; set; }
}
public class Garment
{
[DataType(DataType.Date)]
public DateTime? DateRetired { get; set; }
[Required]
public string Barcode { get; set; }
[Required]
public bool Adults { get; set; }
}
In your CustomerGarment class, you should have:
public class CustomerGarment
{
public int CustomerGarmentId { get; set; }
public int CustomerId { get; set; }
public int OrderId { get; set; }
public int GarmentJacketId { get; set; }
public int GarmentShirtId { get; set; }
public virtual Customer Customer { get; set; }
public virtual Order Order { get; set; }
public virtual GarmentJacket GarmentJacket { get; set; }
public virtual GarmentShirt GarmentShirt { get; set; }
}
And, then, in your View, your DropDownList will look like:
#Html.DropDownListFor(m => m.GarmentJacketId, (SelectList)ViewBag.GarmentJacket, new {style="width:312px;height:30px;margin-top:2px;margin-bottom:5px"})
Your DropDownList only posts one value, which is the GarmentJacketId. You can't bind that Id to the whole GarmentJacket class.
By the way, you also need to replace your hidden inputs with these:
#Html.HiddenFor(model => model.OrderId)
#Html.HiddenFor(model => model.CustomerId)
I think I know your problem. As you suggested in you comment above you need to post everything you want retained in the view. This is one of the differences beteween webforms and MVC, webforms has viewstate that could contain information that you don't explicitly add to the view and post back, giving the impression of state. In MVC you have to add it to the view.
On the other hand you don't need to pass in more information than you need either. You pass inn the customerId as a hidden field. On post method you get the customer from the db using the Id, then you add the order to the customer.
I have some questions about your design, but given that a customer holds a collection of Orders, you could do something like this:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddGarments(CustomerGarment cg)
{
// Get the customer from the database
var customer = db.Customers.Find(c=>c.id==cb.Customer.Id)
var order = new Order();
//Create your order here using information from CustomerGarment model
//If the model already holds a valid Order object then just add it.
//i.e. you could get a Garment object from the DB using the GarmentId from
//the ViewModel if you really need more than just the Id to create the order
customer.Orders.Add(order);
db.SaveChanges();
return RedirectToAction("Index");
}

MVC4: Modelstate is not valid when creating a new record

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.

MVC: Updating a FK Relationship from a SelectList

I'm playing with MVC4 and EF Code First and I'm having a problem getting a DropDownList to update a foreign key of a model type.
Here are my models:
public class Customer
{
public int CustomerID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Party
{
public int PartyID { get; set; }
public virtual Customer Host { get; set; }
public string Location { get; set; }
public DateTime Date { get; set; }
}
So a Party has a Host which is a Customer
In the controller for the /Party/Create GET method, I'm passing a list of customers in the ViewBag:
ViewBag.Customers = new SelectList(db.Customers, "CustomerID", "FirstName");
And then in the View I'm rendering this as a DropDownList:
#Html.DropDownListFor(model => model.Host, ViewBag.Customers as SelectList, String.Empty)
This all works fine, I can choose from a list of customers in the dropdown. However, when I submit the form, the /Party/Create POST method doesn't insert the data because the ModelState is not valid. Looking at the ModelState, I can see that the CustomerID of the chosen Customer is present, but it is failing to be converted from a String to an object of type Customer
What have I missed?
Try binding the DropDownList to the CustomerID property of the Host:
#Html.DropDownListFor(
model => model.Host.CustomerID,
ViewBag.Customers as SelectList,
String.Empty
)

asp.net mvc3 Bind Exclude on properties does not work

I have a class, which has 8 properties / 8 columns in DB. In the Edit page, I want to exclude the AddedDate and UserID fields. When a user edits a voucher, he can't overwrite the AddedDate or UserID values in the DB.
public class Voucher
{
public int ID { get; set; }
public string Title { get; set; }
public string SiteName { get; set; }
public string DealURL { get; set; }
public DateTime AddedDate { get; set; }
public DateTime? ExpirationDate { get; set; }
public string VoucherFileURL { get; set; }
public Guid UserID { get; set; }
}
Here is what I have for Edit controller:
// POST: /Voucher/Edit/5
[HttpPost]
public ActionResult Edit([Bind(Exclude = "AddedDate")]Voucher voucher)
{
if (ModelState.IsValid)
{
db.Entry(voucher).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(voucher);
}
On Edit page, when I click on submit, I got the following error:
System.Data.SqlServerCe.SqlCeException: An overflow occurred while converting to datetime.
Seems like the AddedDate didn't get excluded from the voucher object and triggered the error.
Would you please let me know how to fix it? Thanks!
(it is an updated version of asp.net mvc3 UpdateModel exclude properties is not working, I will go with another approach)
Never use your domain entities as action arguments and never pass your domain entities to your views. I would recommend you to use view models. In the view model you will include only the properties that you want to be bound from the view. The view model is a class that's specifically tailored to the requirements of a given view.
public class VoucherViewModel
{
public int ID { get; set; }
public string Title { get; set; }
public string SiteName { get; set; }
public string DealURL { get; set; }
public DateTime? ExpirationDate { get; set; }
public string VoucherFileURL { get; set; }
}
and then:
[HttpPost]
public ActionResult Edit(VoucherViewModel model)
{
// TODO: if the view model is valid map it to a model
// and pass the model to your DAL
// To ease the mapping between your models and view models
// you could use a tool such as AutoMapper: http://automapper.org/
...
}
UPDATE:
In the comments section #Rick.Anderson-at-Microsoft.com points out that while I have answered your question I haven't explained where the problem comes from.
The thing is that DateTime is a value type meaning it will always have a value. The [Bind(Exclude = "AddedDate")] works perfectly fine and it does what it is supposed to do => it doesn't bind the AddedDate property from the request. As a consequence the property will have its default value which for a DateTime field is 1/1/0001 12:00:00 AM and when he attempts to save this in SQL Server it blows because SQL Server doesn't support such format.

Resources