This question already has answers here:
MVC5 - How to set "selectedValue" in DropDownListFor Html helper
(5 answers)
Closed 5 years ago.
I'm trying to populate the edit view with data which has dropdownlists using ViewModel . The data populates but the Dropdownlists are not selected as per the data.
Have checked similar issues in SO like [SO1][1] , SO2 , SO3 but not able to resolve . Know it might be something silly I'm missing but unable to find.
Code:
Viewmodel:
public class ProductVM
{
public int ID { get; set; }
[Required]
[DisplayName("Product Name")]
public string ProductName { get; set; }
public int SupplierID { get; set; }
public IEnumerable<SelectListItem> Suppliers { get; set; }
public Supplier Supplier { get; set; }
public int UnitID { get; set; }
public IEnumerable<SelectListItem> Units { get; set; }
public Unit Unit { get; set; }
public int ItemCategoryID { get; set; }
public IEnumerable<SelectListItem> Categories { get; set; }
[DisplayName("Categories")]
public ItemCategory ItemCategory { get; set; }
}
Controller Edit :
public ActionResult Edit(int id)
{
var productVM = GetProductById(id);
return View(productVM);
}
private ProductVM GetProductById(int id)
{
Product product = db.Products.Find(id);
var productVM = new ProductVM();
var suppliers = new SelectList(db.Suppliers, "ID", "SupplierName", product.SupplierID);
productVM.Suppliers = suppliers.ToList();
var categories = new SelectList(db.ItemCategories, "ID", "Name", product.ItemCategoryID);
productVM.Categories = categories.ToList();
var units = new SelectList(db.Units, "ID", "Name", product.UnitID);
productVM.Units = units.ToList();
}
View :
<div class="form-group">
#Html.LabelFor(model => model.ProductName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ProductName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ProductName, "", new { #class = "text-danger" })
</div>
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.SupplierID, "SupplierID", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(u => u.SupplierID, Model.Suppliers, "--Select--")
#Html.ValidationMessageFor(model => model.SupplierID, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.UnitID, "UnitID", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(u => u.UnitID, (IEnumerable<SelectListItem>)Model.Units, "--Select--")
#Html.ValidationMessageFor(model => model.UnitID, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ItemCategoryID, "ItemCategoryID", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(u => u.ItemCategoryID, (IEnumerable<SelectListItem>)Model.Categories, "--Select--")
#Html.ValidationMessageFor(model => model.ItemCategoryID, "", new { #class = "text-danger" })
</div>
</div>
Appreciate any help. Thanks in advance.
[1]: Binding Viewmodel Property to dropdown selected item in razor
to-dropdown-selected-item-in-razor
Whatever you set in the selectedValue parameter in the SelectList is going to be ignored. Why? Because you have a strongly typed view and you are binding SupplierID property of ProductVM to the dropdown. In your GetProductById method, you are not actually populating the productVM.SupplierID, UnitID and ItemCategoryID. So they will have the default int value, 0.
So you can change you method to this:
private ProductVM GetProductById(int id)
{
Product product = db.Products.Find(id);
var productVM = new ProductVM
{
// No need to pass the 4th parameter "selectedValue" in SelectList.
Suppliers = new SelectList(db.Suppliers, "ID", "SupplierName"),
Categories = new SelectList(db.ItemCategories, "ID", "Name"),
Units = new SelectList(db.Units, "ID", "Name"),
// Populate these properties
SupplierID = product.SupplierID,
ItemCategoryID = product.ItemCategoryID,
UnitID = product.UnitID
};
return productVM ;
}
The optional selectedValue parameter is usually used when we're not using strongly typed views. If you were to change your View to the following, then your dropdown would have pre selected the value:
#Html.DropDownList("Supplier", Model.Suppliers, "--Select--")
You would want to set an initial value into your selected values in the controller before passing the model to the view if you want the dropdownlists to load with a pre-selected value. For example:
public ActionResult Edit(int id)
{
var productVM = GetProductById(id);
//Set your pre-determined values here and they will show up on the view once binded
productVM.SupplierID = 1;
productVM.UnitID = 1;
productVM.ItemCategoryID = 1;
return View(productVM);
}
This question does a great job of breaking that process down: How to get DropDownList SelectedValue in Controller in MVC
Related
First of all, excuse me if my English isn't that good.
I'm kind of stuck with this. I want to create a list of jobs and be able to choose which position the employee is in through a dropdown list.
This would be the model I have of the jobs. This is the model that created the connection to the database I'm using.
public partial class jobs
{
public jobs()
{
this.employees= new HashSet<employees>();
}
public int id_job { get; set; }
public string job{ get; set; }//NameJob
public virtual ICollection<employees> employees{ get; set; }
}
But as for the employee model I created a view model as follows.
public class CreateEmployeesViewModel
{
[Required (ErrorMessage = "It is necessary to enter a name")]
[Display(Name = "Name")]
public string Name{ get; set; } //Name
[Required (ErrorMessage = "It is necessary to enter the Last Name ")]
[Display(Name = "LastName")]
public string LName{ get; set; } //LastName
[Required (ErrorMessage = "What's your employee's job title?")]
public int Job{ get; set; } //idjob
}
In my controller, I have the following
//Get Add
public ActionResult Add()
{
return View();
}
And for the post
[HttpPost]
public ActionResult Add(CreateEmployeesViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
using (var db = new DBExerciseEntities())
{
var emp = new employees();//New Employee
emp.id_status = 1;//By default I set the new user as Active
emp.name= model.Name;//Get Name
emp.lname= model.LName;//get Last Name
/*This is what I want to turn into DropDownList. Instead of typing the job "int" I want
*to get the list of registered jobs*/
emp.id_job = model.Job;//get id_job
db.employees.Add(emp);
db.SaveChanges();
}
return Redirect(Url.Content("~/CEmployees/"));
}
The view is as the following
#model Exercise.Models.ViewModels.CreateEmployeesViewModel
#{
ViewBag.Title = "AddUser";
}
<h2>#ViewBag.Title</h2>
<div class="row">
<div class="col-md-12">
#using (Html.BeginForm("Add", "CEmployees", FormMethod.Post, new { }))
{
#Html.AntiForgeryToken()
#Html.ValidationMessage("Error", new { #class = "text-danger", #type = "text" })
#Html.LabelFor(d => d.Name)
#Html.TextBoxFor(d => d.Name, new { #class = "form-control" })
#Html.ValidationMessage("Name", new { #class = "text-danger" })
<br />
#Html.LabelFor(d => d.LName)
#Html.TextBoxFor(d => d.LName, new { #class = "form-control" })
#Html.ValidationMessage("LName", new { #class = "text-danger" })
<br />
<!-- I get the name of the [display] assigned in the model-->
#Html.LabelFor(d => d.Job)
#Html.TextBoxFor(d => d.Job, new { #class = "form-control" })<!--How can I do DropDownList?-->
<!-- mensajes de error -->
#Html.ValidationMessage("Job", new { #class = "text-danger" })
<br />
<div style="text-align:right;">
<input type="submit" class="btn btn-success" value="AddUser" />
</div>
}
</div>
</div>
If you could help me it would be very helpful as those I have seen do so with static lists or are simply confusing.
I just want to mention that I'm relying on a different code to get what I have so far and I'm new to MVC.
It's quite simple. Here is my solution. Hope to help, my friend :))
First, you need to add 1 list jobs in Add controller.
public ActionResult Add()
{
List<SelectListItem> lstJobs = new List<SelectListItem>
{
new SelectListItem { Text = "Select", Value = "0" },
new SelectListItem { Text = "Teacher", Value = "1" },
new SelectListItem { Text = "Engineer", Value = "2" },
new SelectListItem { Text = "Accountant", Value = "3" }
};
ViewData["job"] = lstJobs;
return View();
}
Then, show list job in View.
#using (Html.BeginForm("Add", "Employees", FormMethod.Post, new { }))
{
#Html.AntiForgeryToken()
#Html.ValidationMessage("Error", new { #class = "text-danger", #type = "text" })
#Html.LabelFor(d => d.Name)
#Html.TextBoxFor(d => d.Name, new { #class = "form-control" })
#Html.ValidationMessage("Name", new { #class = "text-danger" })
<br />
#Html.LabelFor(d => d.LName)
#Html.TextBoxFor(d => d.LName, new { #class = "form-control" })
#Html.ValidationMessage("LName", new { #class = "text-danger" })
<br />
<!-- I get the name of the [display] assigned in the model-->
#Html.LabelFor(d => d.Job)
#Html.DropDownList("Job", ViewData["job"] as List<SelectListItem>, new { #class = "form-control" })
#*#Html.TextBoxFor(d => d.Job, new { #class = "form-control" })*#
<!-- mensajes de error -->
#Html.ValidationMessage("Job", new { #class = "text-danger" })
<br />
<div style="text-align:right;">
<input type="submit" class="btn btn-success" value="AddUser" />
</div>
}
</div>
Finally, when you submit the form you will get Job value.
Please, don't use ViewData if you don't have to. Since a view model is in used, we just need to have another property in that view model to contain a list of available job titles.
The View Model
First, I would rename the model to just CreateEmployeeViewModel instead of CreateEmployeesViewModel since you're creating just single employee at a time. And then I would rename some of the properties and remove some unnecessary data annotations:
public class CreateEmployeeViewModel
{
[Required(ErrorMessage = "It is necessary to enter a name")]
public string Name{ get; set; } //Name
[Required(ErrorMessage = "It is necessary to enter the Last Name ")]
[Display(Name = "LastName")]
public string LName{ get; set; } //LastName
[Required(ErrorMessage = "What's your employee's job title?")]
public int SelectedJobId { get; set; } //idjob
public IDictionary<int, string> AvailableJobs { get; set; }
}
Here I used IDictionary to contain the list of available job titles since you have only the id and name. If you have more than 2 properties you want to pass, you can create your own view model class for each available job option and use IEnumerable or ICollection instead.
Initialize View Model in Controller
Next, on the HttpGet Add action method, you need to fetch the list of available job titles from your database and load it up to the view model, and return it to the view:
public ActionResult Add()
{
var vm = new CreateEmployeeViewModel
{
AvailableJobs = new Dictionary<int, string>();
};
using (var db = new DBExerciseEntities())
{
vm.AvailableJobs = db.jobs
.ToDictionary(x => x.id_job, x => x.job);
}
return View(vm);
}
The View
On the view you can use #Html.DropdownListFor() helper to generate the dropdown input for the SelectedJobId.
I've also changed your #Html.ValidationMessage to #Html.ValidationMessageFor().
By the way, I saw you used Bootstrap css classes. Come on! You can craft a better UI with that:
#model Exercise.Models.ViewModels.CreateEmployeeViewModel
#{
ViewBag.Title = "AddUser";
}
<h2>#ViewBag.Title</h2>
#using (Html.BeginForm("Add", "CEmployees", new { area = "" }, FormMethod.Post, new { }))
{
#Html.AntiForgeryToken()
#Html.ValidationMessage("Error", new { #class = "text-danger", #type = "text" })
<div class="form-group">
#Html.LabelFor(d => d.Name)
#Html.TextBoxFor(d => d.Name, new { #class = "form-control" })
#Html.ValidationMessageFor(d => d.Name, new { #class = "text-danger" })
</div>
<div class="form-group">
#Html.LabelFor(d => d.LName)
#Html.TextBoxFor(d => d.LName, new { #class = "form-control" })
#Html.ValidationMessageFor(d => d.LName, new { #class = "text-danger" })
</div>
<div class="form-group">
#Html.LabelFor(d => d.SelectedJobId)
#Html.DropdownListFor(d => d.SelectedJobId,
new SelectList(Model.AvailableJobs, "Key", "Value"),
new { #class = "form-control" }
)
#Html.ValidationMessageFor(d => d.SelectedJobId, new { #class = "text-danger" })
</div>
<div class="text-right">
<button type="submit" class="btn btn-success">Add User</button>
</div>
}
The SelectedJobId should contain user's selection when the form posts back to the server.
I am trying to define ViewModels that faithfully represent the view (to make strict use of that concept).
Some of the elements of the ViewModel are updated dynamically. The problem I have, is that when I do the Post, the ViewModel returns without the elements that were updated dynamically.
The update is done through jQuery, when an event is performed. An action is invoked through Url.Action, and a Div is updated.
I made an example to clarify the scenario. An application that only stores a location (state and city). For this I have three ViewModels: one to represent the States in a SelectList, one to represent the Cities in a SelectList, and finally one to represent the Location (formed by the two ViewModel that I mentioned first).
Models:
public class State
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
public class City
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public int StateId { get; set; }
public virtual State State { get; set; }
}
ViewModels:
public class CitySelectListViewModel
{
public CitySelectListViewModel() { }
public CitySelectListViewModel(IEnumerable<Models.City> cities)
{
this.Cities = cities;
}
[Display(Name = "Cities")]
[Required]
public int? SelectedCityId { get; set; }
public IEnumerable<City> Cities { get; }
}
public class StateSelectListViewModel
{
public StateSelectListViewModel() { }
public StateSelectListViewModel(IEnumerable<State> states)
{
this.States = states;
}
[Display(Name = "States")]
[Required]
public int? SelectedStateId { get; set; }
public IEnumerable<State> States { get; }
}
public class LocationCreateViewModel
{
public LocationCreateViewModel() { }
public LocationCreateViewModel(ICollection<State> states)
{
this.StateSelectListViewModels = new StateSelectListViewModel(states);
this.CitySelectListViewModel = new CitySelectListViewModel();
}
public StateSelectListViewModel StateSelectListViewModels { set; get; }
public CitySelectListViewModel CitySelectListViewModel { set; get; }
}
Location [Controller]:
public class LocationController : Controller
{
private DALDbContext db = new DALDbContext();
// GET: Location/Create
public ActionResult Create()
{
LocationCreateViewModel locationCreateViewModel = new LocationCreateViewModel(db.States.ToList());
return View(locationCreateViewModel);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(LocationCreateViewModel pLocationCreateViewModel)
{
if (ModelState.IsValid)
{
//db.States.Add(state);
//db.SaveChanges();
return RedirectToAction("Index", "Home");
}
LocationCreateViewModel locationCreateViewModel = new LocationCreateViewModel(db.States.ToList());
return View(locationCreateViewModel);
}
public ActionResult CitySelectList(int? stateId)
{
CitySelectListViewModel citySelectListViewModel = new CitySelectListViewModel(db.Cities.Where(c => c.StateId == stateId).ToList());
return View(citySelectListViewModel);
}
}
Create [View]:
#model ViewModelExample.ViewModels.LocationCreateViewModel
....
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>State</h4>
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.StateSelectListViewModels.SelectedStateId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.StateSelectListViewModels.SelectedStateId, new SelectList(Model.StateSelectListViewModels.States, "Id", "Name"), "Select a State", htmlAttributes: new { #class = "form-control", #id = "StateSelectList" })
#Html.ValidationMessageFor(model => model.StateSelectListViewModels.SelectedStateId, "", new { #class = "text-danger" })
</div>
</div>
<div id="CityContainer">
#Html.Action("CitySelectList")
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
<script type="text/javascript">
$(function () {
// Fill City DropDownList
$('#StateSelectList').change(function () {
var selectedStateId = this.value;
$('#CityContainer').load('#Url.Action("CitySelectList")?stateId=' + selectedStateId);
});
});
</script>
}
CitySelectList [View]:
#model ViewModelExample.ViewModels.CitySelectListViewModel
....
<div class="form-group">
#Html.LabelFor(model => model.SelectedCityId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.SelectedCityId, new SelectList(Model.Cities, "Id", "Name"), "Select a City", htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.SelectedCityId, "", new { #class = "text-danger" })
</div>
</div>
I will show the execution of my example, and I will show the problem through the inspection of the ViewModel that I receive after the Post:
I select a State and a City, and I press Create.
I inspect the ViewModel received after the Post. We can see how CitySelectListViewModel is null, and what I want is to bring the last ViewModel that was updated through jQuery.
I admit that I have provided a long example, but it is the only way I found to explain what I need. Thanks in advance.
VS-Project of the example
I'ts because you are preventing the modelBinder to accurately bind to LocationCreateViewModel in your Create action when replacing the inner HTML of <div id="CityContainer"> (thats what you do with $('#CityContainer').load(...). You instruct the model binder to bind to
#model ViewModelExample.ViewModels.CitySelectListViewModel and as a result you get this HTML for the city select list:
One way of solving this is modifying CitySelectList.cshtml to:
#model ViewModelExample.ViewModels.LocationCreateViewModel
#{
Layout = null;
}
<div class="form-group">
#Html.LabelFor(model => model.CitySelectListViewModel.SelectedCityId,
htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model =>
model.CitySelectListViewModel.SelectedCityId, new
SelectList(Model.CitySelectListViewModel.Cities, "Id", "Name"), "Select a City", htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.CitySelectListViewModel.SelectedCityId, "", new { #class = "text-danger" })
</div>
</div>
and your CitySelectList action to:
public ActionResult CitySelectList(int? stateId)
{
LocationCreateViewModel locationCreateViewModel = new LocationCreateViewModel();
locationCreateViewModel.CitySelectListViewModel = new CitySelectListViewModel(db.Cities.Where(c => c.StateId == stateId).ToList());
return View(locationCreateViewModel);
}
But I would recommend custom model binding as well.
Hi I have the following model:
public class ActiveProducts
{
public int ID { get; set; }
public virtual Product Product { get; set; }
}
and for data context I have:
public DbSet<ActiveProduct> ActiveProducts { get; set; }
I wanted to create a controller and views to do the CRUD operations
How would I be able to generate a view for the create and edit so that I have a drop down list of products?
I used the standard method of creating a controller using the model, with the CRUD operations and it didn't render out anything for the create.
Okay, since Product_ID holds the primary key value of the Products table, then this should work in your controller:
public ActionResult Create()
{
ViewBag.Product_ID = new SelectList(db.Product, "ID", "Product");
}
[HttpPost]
public ActionResult Create([Bind(Include = "ID, Product_ID" /*etc*/)] ActiveProducts activeProducts)
{
ViewBag.Product_ID = new SelectList(db.Product, "ID", "Product", activeProducts.Product_ID);
}
then in your Create View:
<div class="form-group">
#Html.LabelFor(model => model.Product_ID, "Product:", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("Product_ID", null, "-- Select Product --", htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Product_ID, "", new { #class = "text-danger" })
</div>
</div>
I am currently working on a project to model a bikestore. In my 'Order' object, I have a lis object for the Bike items on the order. How would I add bikes to this list? I.E I want to display a list of availiable bikes in the Create view, an add one or more of them to the order.
My Controller:
public ActionResult Create()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "OrderNumber,CustomerName,OrderDate,PickupDate,TotalCost,PaymentMethod")] Order order)
{
if (ModelState.IsValid)
{
db.Orders.Add(order);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(order);
}
My Inventory model
public class Inventory
{
public int Id { get; set; }
public string SerialNumber { get; set; }
public virtual Store Store { get; set; }
public int? StoreId { get; set; }
public string Model { get; set; }
public string Description { get; set; }
public Decimal InventoryCost { get; set; }
public Decimal RecSalePrice { get; set; }
public Decimal SalePrice { get; set; }
public string PaymentMethod { get; set; }
public virtual BikeCategory Category { get; set; }
public int? CategoryId { get; set; }
}
My Order model:
namespace BikeStore.Models
{
public class Order
{
public Order()
{
OrderedItems = new List<Inventory>();
}
public string CustomerName { get; set; } //FROM CONTROLLER User.Identity.Name
public virtual List<Inventory> OrderedItems { get; set; }
[Key, DatabaseGenerated(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity)]
public int OrderNumber { get; set; }
In the create view for orders:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Order</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.CustomerName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.CustomerName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.CustomerName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.OrderDate, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.OrderDate, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.OrderDate, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.PickupDate, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.PickupDate, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.PickupDate, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.TotalCost, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.TotalCost, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.TotalCost, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.PaymentMethod, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.PaymentMethod, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.PaymentMethod, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Start by creating view models to represent what you want to display/edit in the view (add display and validation attributes as appropriate)
public class InventoryVM
{
public int ID { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
}
public class OrderVM
{
public string PaymentMethod { get; set; }
public List<InventoryVM> Inventory { get; set; }
}
Note that CustomerName, OrderDate and Total are not appropriate (you don't want a user editing these - they should be set in the POST method immediately before saving the order). Not sure what PickupDate represents but if its the actual date then that's not appropriate either (it would be set separately when the order is collected). I would also suggest that PaymentMethod be an enum or collection of PaymentType's and that you and use a dropdownlist in the view for selection.
Then the GET method would be
public ActionResult Create()
{
// Get all available bikes, for example
var inventory = db.Inventory;
OrderVM model = new OrderVM
{
Inventory = inventory.Select(i => new
{
ID = i.ID,
Name = i.Model // modify this to suit what you want to display in the view
}).ToList()
};
return View(model);
}
And in the view
#model yourAssembly.OrderVM
#using (Html.BeginForm())
{
for(int i = 0; i < Model.Inventory.Count; i++)
{
#Html.HiddenFor(m => m.Inventory[i].ID)
#Html.CheckBoxFor(m => m.Inventory[i].IsSelected)
#Html.LabelFor(m => m.Inventory[i].IsSelected, Model.Inventory[i].Name)
}
#Html.TextBoxFor(m => m.PayentMethod)
<input type="submit" value="Create" />
}
And the POST method would be
public ActionResult Create(OrderVM model)
{
// Initialize a new Order and map properties from view model
var order = new Order
{
CustomerName = User.Identity.Name,
OrderDate = DateTime.Now,
....
PaymentMethod = model.PaymentMethod
}
// Save the order so that you now have its `ID`
IEnumerable<int> selectedItems = model.Inventory.Where(i => i.IsSelected).Select(i => i.ID);
foreach(var item in selectedItems)
{
// You have not shown the model for this so just guessing
var orderItem = new OrderItem{ OrderID = order.Id, InventoryId = item };
db.OrderItems.Add(orderItem);
}
db.SaveChanges();
}
Side notes:
If you want to be able to allow users to select more that one of any
item, the you could change bool IsSelected to say int Quantity
If you you want to display additional information about the items,
say Description and Cost you can include additional properties
in the InventoryVM view model and display them with
#Html.DisplayFor(m => m.Inventory[i].Description)
If you want to display a total cost for all selected items in the
view, you will need to use javascript/jquery
If ModelState could be invalid, you will need to repopulate the
properties of InventoryVM before you return the view (as shown
only the ID and IsSelected properties post back) or include
hidden inputs for the other properties in the view
Having some trouble understanding how to create and edit a collection of strings using a form. I've tried using EditorFor but it seems to have no luck and instead puts the following text into the form. I'm trying to edit the collection "Keywords".
System.Collections.Generic.HashSet`1[MVCModuleStarter.Models.Module]System.Collections.Generic.HashSet`1[MVCModuleStarter.Models.Module]
This is the Html I'm using the EditorFor in with a working EditorFor being used on a string for reference.
<div class="form-group">
#Html.LabelFor(model => model.Category, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Category, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Category, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Keywords, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Keywords, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Keywords, "", new { #class = "text-danger" })
</div>
</div>
This is the Edit method inside my controller;
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "ModuleId,ModuleTitle,ModuleLeader,ModuleDescription,ImageURL,Category,Keywords")] Module module)
{
if (ModelState.IsValid)
{
int moduleId = module.ModuleId;
repository.UpdateModule(module);
repository.Save();
return RedirectToAction("Details", new { Id = moduleId });
}
return View(module);
}
This is the Model for reference;
[Required, StringLength(20), Display(Name = "Category")]
public string Category { get; set; }
public virtual ICollection<Keyword> Keywords { get; set; }
Model for Keyword
public class Keyword
{
[Key, Display(Name = "ID")]
public int KeywordId { get; set; }
[Required, StringLength(100), Display(Name = "Keyword")]
public string KeywordTerm { get; set; }
public virtual ICollection<Module> Modules { get; set; }
}
}
Any help would be amazing, still new to this! Thanks!
You need to create an EditorTemplate for Keyword, for example
In /Views/Shared/EditorTemplates/Keyword.cshtml (add divs, class names etc as required)
#model Keyword
#Html.HiddenFor(m => m.KeywordId)
#Html.LabelFor(m => m.KeywordTerm)
#Html.TextBoxFor(m => m.KeywordTerm)
#Html.ValidationMessageFor(m => m.KeywordTerm)
Then in the main view
Html.EditorFor(m=> m.Keywords)
Note I have omitted the collection property Modules, but if you also want to edit them, the add another EditorTemplate for Modules
Alternatively you can use a for loop in the main view. This would mean the collection need to be IList<T>
for(int i = 0; i < Model.Keywords.Count, i++)
{
#Html.HiddenFor(m => m.Keywords[i].KeywordId)
// other properties of Keyword
for (int j = 0; j < Model.Keywords[i].Modules.Count; j++)
{
#Html.TextBoxFor(m => m.Keywords[i].Modules[j].SomeProperty)
// other properties of Module
}
}