Mvc 5 - 2 model in view - asp.net-mvc

I just started with MVC and I'm trying to learn how to create an order and order details project.
I have also an Inventory which contains all the items in there but I am facing some problems trying to pull the data out from the inventory through the order details.
How do I combine this two together?
#model InventoryTest.Models.Inventory.order and
#model IEnumerable< InventoryTest.Models.Inventory.Inventories> in the view code?
I apologies for the messy structure of the code as I'm still learning but I do hope that someone could advice me on the problem I'm facing.
Inventory Model:
public int InventoryID { get; set; }
public string ItemNo { get; set; }
public string Item { get; set; }
public int Quantity { get; set; }
Order Model:
public int OrderID { get; set; }
public DateTime Date { get; set; }
public int EmployeeID { get; set; }
public int DepartmentID { get; set; }
public IEnumerable<SelectListItem> GetEmployee()
{
var query = db.Employees.Select(c => new SelectListItem
{
Value = c.EmployeeID.ToString(),
Text = c.DisplayName,
});
return query.AsEnumerable();
}
public IEnumerable<SelectListItem> GetDeptList()
{
var query = db.Departments.Select(c => new SelectListItem
{
Value = c.DepartmentID.ToString(),
Text = c.Description,
});
return query.AsEnumerable();
}
Order Detail Model:
public int OrderDetailID { get; set; }
public int Quantity { get; set; }
public int OrderID { get; set; }
public int InventoryID { get; set; }
On my view code for order create is as follows:
#model InventoryTest.Models.Inventory.Order
#{
ViewBag.Title = "Order Forms";
}
<h2>Order Forms</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="row">
<div class="col-sm-4">
#Html.LabelFor(model => model.EmployeeID, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-offset-4">
#Html.ValidationMessageFor(model => model.EmployeeID, "", new { #class = "text-danger" })
#Html.DropDownListFor(m => m.EmployeeID, Model.GetEmployee(), "Please Select", new
{
#style = "width: 200px;height:35px",
#class = "input-select",
#data_bv_notempty = "true",
#data_bv_message = "Please select project."
})
</div>
</div>
<div class="col-sm-4">
#Html.LabelFor(model => model.Department, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-offset-4">
#Html.ValidationMessageFor(model => model.DepartmentID, "", new { #class = "text-danger" })
#Html.DropDownListFor(m => m.DepartmentID, Model.GetDeptList(), "Please Select", new
{
#style = "width: 200px;height:35px",
#class = "input-select",
#data_bv_notempty = "true",
#data_bv_message = "Please select project."
})
</div>
</div>
</div>
</div>
<hr />
}
<br />
<h4>Item Listing</h4>
<table class="table table-hover">
<tr>
<th>S/N</th>
<th>Item No.</th>
<th>Item</th>
<th>Quantity</th>
</tr>
*//I want to use foreach to populate the data here*
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</table>
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}

Create a view model class:
public class OrderAndInventoryViewModel
{
public Order OrderInfo { get; set; }
public IEnumerable<Inventories> InventoryInfo { get; set; }
}
And in your view, use:
#model <your namespace goes here>.OrderAndInventoryViewModel
Assign the appropriate order and inventory information into the OrderAndInventory object in your controller, and pass that to the view.

One solution would be to create a new object, call it something like "OrderViewModel". That object could contain your Order and Inventory list. Then pass OrderViewModel to the View...
namespace InventoryTest.Models.Inventory {
public class OrderViewModel {
public Order order { get; set; }
public IEnumerable<InventoryTest.Models.Inventory.Inventories> inventories { get; set; }
}
}
Then the #model at the top of the View would be
#model InventoryTest.Models.Inventory.OrderViewModel
In the View, you can access the EmployeeID as...
Model.order.EmployeeID

Related

complex type model wont pass list propery

hi guys i am having trouble with my mvc app. its a simple quiz app and i am stuck at creating create view for question model.
I have Question and Option model with appropriate view models(in my case they are QustionDTO and OptionDTO) and i want to make cshtml create view for Question with list of Options.like this but when i submit form, my list of options is null.
this is my Question and Option model
public class Question
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public string QuestionText { get; set; }
public virtual ICollection<Option> Options { get; set; }
}
public class Option
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
[Display(Name ="Answer text")]
public string OptionText { get; set; }
[Required]
public bool IsCorrect { get; set; }
}
this is my DTO models
public class QuestionDTO
{
public int Id { get; set; }
public string QuestionText { get; set; }
public List<OptionDTO> Options { get; set; }
}
public class OptionDTO
{
public int Id { get; set; }
public string OptionText { get; set; }
public bool IsCorrect { get; set; }
}
and this is my view with editor template located in "~/views/shared/editortemplate/OptionDTO.cshtml"
#model Quiz.BusinessEntites.QuestionDTO
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>QuestionDTO</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.QuestionText, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.QuestionText, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.QuestionText, "", new { #class = "text-danger" })
</div>
</div>
<table class="table" style="width:50%">
#for (int i = 0; i < 3; i++)
{
#Html.EditorFor(model=>model.Options[i])
}
</table>
<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>
this is OptionDTO editor template
#using Quiz.BusinessEntites
#model Quiz.BusinessEntites.OptionDTO
<tr>
<th class="col-md-2">
#Html.DisplayNameFor(m => m.OptionText)
</th>
<th class="col-md-2">
#Html.DisplayNameFor(m => m.IsCorrect)
</th>
</tr>
<tr>
<td class="col-md-2">
#Html.EditorFor(m => m.OptionText)
</td>
<td class="col-md-2">
#Html.EditorFor(m => m.IsCorrect)
</td>
</tr>
from the image above u can see that options list is null. if u have any suggestion it will be appreciated.
In your http post action method, the Bind attribute with Include list is telling the Model binder to bind only "Id","QuestionText" and "IsCorrect" properties of QuestionDto object from the posted form data. So the model binder will not bind the Options property value.
Remove the Bind attribute from your Http post action method.
There is no need to use the Bind attribute if your view model is specific to your view, means you have only properties needed for your view (In your case it looks like so)
public ActionResult Create(QuestionDTO model)
{
// to do :return something
}
If you want to use a non view specific view model, but still want to use Bind attribute to specify only subset of properties, Include just those properties. In your case, your code will be like
public ActionResult Create([Bind(Include="Id,QuestionText",Options"] QuestionDTO model)
{
// to do :return something
}
Also you should editer template view should be in a directory called EditorTemplates , not EditorTemplate

Categories/Subcategories in asp.net mvc

We are making a marketplace like https://www.etsy.com/. And we have a problem in categorising the listings. We want to categories the item in the Listing in 3 levels, f.ex it has to be categories in this order:
Category 1
Sub Category 1.1
Sub Category 1.1.1
One of the important thing is that when you choose a category, f.ex. Electronics, then in the subcategory you can only see stuff like pc, smartphone, tv etc.
This is what we have now
public class Listing
{
public int ListingId { get; set; }
public String Name { get; set; }
public int Subcategory2Id { get; set; }
public virtual Subcategory2 Subcategory2 { get; set; }
}
public class Category
{
public int CategoryId { get; set; }
public String CategoryName { get; set; }
public virtual ICollection<Subcategory1> Subcategory1s { get; set; }
}
public class Subcategory1
{
public int Subcategory1Id { get; set; }
public String Subcategory1Name { get; set; }
public int CategoryId { get; set; }
public virtual Category Categories { get; set; }
public virtual ICollection<Subcategory2> Subcategory2s { get; set; }
}
public class Subcategory2
{
public int Subcategory2Id { get; set; }
public String Subcategory2Name { get; set; }
public int Subcategory1Id { get; set; }
public virtual Subcategory1 Subcategory1s { get; set; }
public virtual ICollection<Listing> Listings { get; set; }
}
and in the IdentityModels-ApplicationDbContext we have
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public DbSet<Listing> Listings { get; set; }
public DbSet<Category> Categories { get; set; }
public DbSet<Subcategory1> Subcategory1s { get; set; }
public DbSet<Subcategory2> Subcategory2s { get; set; }
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
The thing is we are not sure this is he right way to do it, and we dont know how to proceed, the meaning is that when you create a listing you have to have 3 drop down list, where you choose the respective categorys. So first you choose your category, and then you are able to select the subcategory 1 etc...
You should absolutely not have multiple category/subcategory entities. A category can have a parent and it can have children, but they're all "categories".
public class Category
{
public int Id { get; set; }
public int? ParentId { get; set; }
public virtual Category Parent { get; set; }
public virtual ICollection<Category> Children { get; set; }
}
ParentId is nullable, because top-level categories have no parent.
Entity Framework tends to get confused by self-referencing relationships, so you might need a little fluent config to help it out:
public class Category
{
// properties
public class Mapping : EntityTypeConfiguration<Category>
{
public class Mapping()
{
HasOptional(m => m.Parent).WithMany(m => m.Children);
}
}
}
Then, in your context:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new Category.Mapping());
}
With all that in place, when you're in your "Electronics" category, you'd show the subcategories simply by iterating over it's Children property.
UPDATE
If you need the full hierarchy rather than just one level at a time, you have a couple of options. First, you can just include multiple levels when querying:
db.Categories.Include("Children.Children");
That's not highly efficient, though, and I definitely would not recommend delving much deeper than tertiary children. However, that's all you're asking for, so this is still a workable method.
Second, you can create a stored procedure to walk the hierarchical structure for you. It's a little more complex, but with a combination of WITH and UNION ALL, you can create a flat representation of the hierarchy and then recursively use LINQ's GroupBy to work it back into a hierarchical structure.
There's a final potential third option in #Hackerman's recommendation of using HIERARCHYID, but unfortunately, to do that, you must completely remove Category from your EF context, which also means removing any direct relationships to it, as well. To relate a product to a category, you could only store the id (not as a foreign key), and then use that id to manually lookup the category in a second step. Unfortunately, while this solution makes dealing the the hierarchy easier, it makes doing everything else more difficult. Either way, it's up to you, though.
This seems to be a correct solution.
You can also use only one class (one DB table etc.) for all categories. Your "Category" class/table must then contain the reference of the parent category (nullable). That allows to make generic treatments for all categories.
For example, when the user create an item, you can display a dropdown list for the main category. If the user selects a category which contains other category, an other dropdownlist is displayed with the child categories, etc...
I giving here a example for category and subcategory with image upload.
public class ProductController : Controller
{
ApplicationDbContext db = new ApplicationDbContext();
// GET: Product
public ActionResult Index()
{
return View();
}
public ActionResult insert(int? id)
{
ViewBag.categoryList = db.Product.Where(x => x.CategoryId == 0).Select(x => new SelectListItem { Text = x.name, Value = x.Id.ToString() }).ToList();
var product = db.Product.Where(x => x.Id == id).Select(x => x).FirstOrDefault();
if (product == null) { product = new Product(); product.CategoryId = 0; }
return View(product);
}
[HttpPost]
public ActionResult insert(Product model)
{
if (Request.Files.Count > 0)
if (Request.Files["fileupload"].ContentLength > 0)
{
var fileupload = Request.Files[0];
var fileName = Path.GetFileName(fileupload.FileName);
model.Imagename = fileName;
model.ImageUrl = DateTime.Now.Ticks.ToString() + "." + fileName.Split('.')[1];
string baseurl = Server.MapPath("/") + "Images/" + model.ImageUrl;
fileupload.SaveAs(baseurl);
}
if (model.Id > 0)
{
var productEntity = db.Product.Where(x => x.Id == model.Id).Select(x => x).FirstOrDefault();
if (model.Imagename != null)
productEntity.Imagename = model.Imagename;
if (model.ImageUrl != null)
productEntity.ImageUrl = model.ImageUrl;
productEntity.name = model.name;
productEntity.CategoryId = model.CategoryId;
}
else
{
db.Product.Add(model);
}
db.SaveChanges();
return RedirectToAction("Index");
}
public ActionResult ProductList()
{
var product = db.Product.Where(x => x.Id > 0).Select(x => x).ToList();
return View(product);
}
public ActionResult getsubcategory(int id)
{
var list = db.Product.Where(x => x.CategoryId == id)
.Select(x => new SelectListItem { Text = x.name, Value = x.Id.ToString() }).ToList();
return Json(list, JsonRequestBehavior.AllowGet);
}
}
This upper controller for insert update record.
Below html code :
#model WebApplication1.Models.Product
#{
ViewBag.Title = "insert";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>insert</h2>
#using (Html.BeginForm("insert","product", FormMethod.Post,new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Product</h4>
<hr />
#Html.HiddenFor(x=>x.Id)
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
<label class="control-label col-md-2">SubCategory</label>
<div class="col-md-10">
#Html.DropDownList("SubCategory", new SelectList(ViewBag.categoryList, "Value", "Text", Model.CategoryId), "-Select-", new { #onchange = "categoryselect()", htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.CategoryId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.CategoryId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.CategoryId, new SelectList(ViewBag.categoryList, "Value", "Text", Model.CategoryId),"-Select-", new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.CategoryId, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextBoxFor(model => model.name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Imagename, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
<input id="Imagename" name="fileupload" type="file" class = "form-control" />
#*#Html.(model => model.Imagename, new { htmlAttributes = new { #class = "form-control" } })*#
#Html.ValidationMessageFor(model => model.Imagename, "", 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>
<script>
function categoryselect () {
var d = $("#SubCategory option:selected").val();
$.ajax({
url: "/product/getsubcategory?id="+d
, type: "get"
, success: function (data) {
// alert(data)
$("#CategoryId").html('<option value="">-select- </option>');
for(var i=0;i<data.length;i++)
$("#CategoryId").append('<option value="' + data[i].Value + '">' + data[i].Text + '</option>')
}
})
}
</script>
model:
namespace WebApplication1.Models
{
public class Product
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public int CategoryId { get; set; }
public string name { get; set; }
public string ImageUrl { get; set; }
public string Imagename { get; set; }
}
public class Category
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public int PrentId { get; set; }
public string name { get; set; }
}
}
Index Page:
#{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Index</h2>
<div id="productList">
</div>
<script src="https://code.jquery.com/jquery-1.9.1.min.js"></script>
<script>
$(document).ready(function () {
$.ajax({
url:"/product/productlist"
, type: "GET"
,success:function(data)
{
$("#productList").html(data)
}
})
})
</script>
List Page:
#model IEnumerable<WebApplication1.Models.Product>
<p>
#Html.ActionLink("Create New", "Insert")
</p>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.CategoryId)
</th>
<th>
#Html.DisplayNameFor(model => model.name)
</th>
<th>
#Html.DisplayNameFor(model => model.ImageUrl)
</th>
<th>
#Html.DisplayNameFor(model => model.Imagename)
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.CategoryId)
</td>
<td>
#Html.DisplayFor(modelItem => item.name)
</td>
<td>
#Html.DisplayFor(modelItem => item.ImageUrl)
</td>
<td>
#Html.DisplayFor(modelItem => item.Imagename)
</td>
<td>
#Html.ActionLink("Edit", "insert", new { id=item.Id })
</td>
</tr>
}
</table>

On posting to server all collections of complex types are null in mvc

I'm fairly new to MVC but am progressing.
I have come across an issue that I can't seem to solve and would be greatful of any assistance.
When I post to the server my edits (in RoutineViewModel) are mostly lost, primitive data types are persisted (in class Routine) but collections of complex types (ICollection<RoutineExercise>) are lost.
I found this MVC Form not able to post List of objects and followed the advice to seperate the view into an EditorTemplate but this has not worked. Using the '#foreach' loop still produces all the page controls with the same id and name when you viewsource. I tried using a for (int i = 1; i <= 5; i++) type loop as many other posts suggest but get errors about not being able to apply index to my object.
Also the fact this #Html.DropDownListFor(model => Model.ExerciseId, Model.Exercises, "", new { #class = "input-sm col-md-12" }) does not select the correct list item (Model.ExerciseId has the correct value) concerns me.
Any help/advice would be great as I'm stuck and have been for 3 days now.
* POCO *
public partial class Routine
{
public Routine()
{
this.RoutineExercises = new List<RoutineExercise>();
}
public int Id { get; set; }
public string RoutineName { get; set; }
public string Description { get; set; }
...Other fields removed for clarity...
public virtual ICollection<RoutineExercise> RoutineExercises { get; set; }
}
public partial class RoutineExercise
{
public int Id { get; set; }
public int RoutineId { get; set; }
public int Exerciseid { get; set; }
public int SetsToDo { get; set; }
public int RepsToDo { get; set; }
...Other fields removed for clarity...
public virtual Exercise Exercise { get; set; }
public virtual Routine Routine { get; set; }
}
* VIEWMODEL *
public class RoutineViewModel
{
//Routine information
public int Id { get; set; }
[Display(Name = "Name")]
public string RoutineName { get; set; }
public string Description { get; set; }
//Exercise information
[Display(Name = "Exercise")]
public ICollection<RoutineExercise> RoutineExercises { get; set; }
public IEnumerable<SelectListItem> Exercises { get; set; }
public int ExerciseId { get; set; }
}
* FORM *
<div class="panel-body">
#using (Html.BeginForm("Edit", "Workout"))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.Id)
#Html.EditorForModel()
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
</div>
* EDITOR TEMPLATE *
<div class="form-group">
#Html.LabelFor(model => model.RoutineName, new { #class = "control-label col-md-1" })
<div class="col-md-2">
#Html.EditorFor(model => model.RoutineName)
#Html.ValidationMessageFor(model => model.RoutineName)
</div>
#Html.LabelFor(model => model.Description, new { #class = "control-label col-md-1" })
<div class="col-md-2">
#Html.EditorFor(model => model.Description)
#Html.ValidationMessageFor(model => model.Description)
</div>
</div>
#foreach (var e in Model.RoutineExercises)
{
#Html.LabelFor(model => model.RoutineExercises, new { #class = "control-label col-md-1" })
<div class="col-md-3">
#*TO FIX This does NOT bind the selected value*#
#Html.DropDownListFor(model => Model.ExerciseId, Model.Exercises, "", new { #class = "input-sm col-md-12" })
</div>
<div class="col-md-12">
#Html.LabelFor(model => e.SetsToDo, new { #class = "control-label col-md-2" })
#Html.EditorFor(m => e.SetsToDo, new { #class = "control-label col-md-10" })
</div>
}
* CONTROLLER *
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(RoutineViewModel rvm) /*rvm always null for collections only*/
{
if (ModelState.IsValid)
{
//Save Routine
var r = new Routine
{
Id = rvm.Id,
RoutineName = rvm.RoutineName,
Description = rvm.Description,
RoutineFrequencyId = rvm.RoutineFrequencyId,
RoutineLengthId = rvm.RoutineLengthId
};
_repo.Update(r);
return RedirectToAction("Index");
}
return View(getRoutineViewModel(rvm.Id));
}
First, avoid the term "complex type" unless you're actually talking about a complex type in Entity Framework. It just creates confusion, and honestly, nothing you have here is really "complex" anyways.
You will indeed need to employ a for loop with an index instead of foreach to get the proper field names for the modelbinder to work with. However, the reason you're getting an error is that ICollection is not subscriptable ([N]). You can use ElementAt(N) to pull out the item at an index, but unfortunately, Razor will still not create the right field names with that. As a result, you need to use something like List for your collection properties to edit them inline. Since you're already using a view model this is trivial. Just change the property type from ICollection<RoutineExcercise> to List<RoutineExcercise> on your view model.

Adding/Update Related Data ASP.NET MVC 5

I'm new to programming so I'm still learning.
I need to add Items to Grocery from a single view. But I can't get the data to save.
When I hit save I don't get any exceptions, the page just loads but nothing is saved to the
database. Can I get some help/guidance as to what I am doing wrong?
Data Class
public class Grocery
{
public int Id { get; set; }
public string Name { get; set; }
public virtual List<Item> Items { get; set; }
}
public class Item {
public int ItemId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
View Model
public class GroceryViewModel
{
public Grocery Grocery { get; set; }
public virtual Item Item { get; set; }
public GroceryViewModel(int GroceryId)
{
using (var db = new MyContext())
{
Grocery = db.Groceries
.Include("Items")
.SingleOrDefault(a => a.Id == GroceryId);
}
}
}
Controller
public ActionResult Edit(int GroceryId, GroceryViewModel groceryViewModel)
{
var model = new GroceryViewModel(GroceryId);
var plusItems = new Item
{
Name = groceryViewModel.Item.Name,
Description = groceryViewModel.Item.Description,
};
db.Items.Add(plusItems);
db.SaveChanges();
return RedirectToAction(model);
View
#model Project.Models.GroceryViewModel
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Groceries</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Item.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Item.Name)
#Html.ValidationMessageFor(model => model.Item.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Item.Description)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Item.Description)
#Html.ValidationMessageFor(model => model.Description)
</div>
<p>
<input type="submit" value="Add Item" />
</p>
</fieldset>
try
using (var db = new MyContext())
{
var grocery = db.Groceries.Single(a => a.Id == groceryId);
var plusItems = new Item
{
Name = groceryViewModel.Item.Name,
Description = groceryViewModel.Item.Description,
};
grocery.Items.Add(plusItems);
db.SaveChanges();
}

Composite ViewModel and UpdateModel

What is missing that restuls in unpopulated values in POST action?
Controller
public ActionResult Index()
{
var productPageViewModel = new ProductPageViewModel();
productPageViewModel.ProductPageCriteria = BuildProductPageCriteriaViewModel();
productPageViewModel.Products = GetProducts(productPageViewModel.ProductPageCriteria);
return View(productPageViewModel);
}
[HttpPost]
public ActionResult Index(ProductPageViewModel productPageViewModel, FormCollection formCollection)
{
// productPageViewModel is not populated with posted values of ProductPageCriteria.CategoryID, ProductPageCriteria.DepartmentID and ProductPageCriteria.PageSize
// formCollection has correct values
// Calling UpdateModel(productPageViewModel); has no affect - makes sense, the framework has already called it
// Calling UpdateModel(productPageViewModel.ProductPageCriteria); populates the values.
// The renderd form has names like CategoryID, DepartmentID unlike ProductPageCriteria.CategoryID, ProductPageCriteria.DepartmentID
// if the top model was passed to all partial views also.
return View(productPageViewModel);
}
Models
public class ProductPageCriteriaViewModel
{
public const int DefaultPageSize = 15;
public ProductPageCriteriaViewModel()
{
Categories = new List<Category>();
Departments = new List<Department>();
PageSize = DefaultPageSize;
}
[Display(Name = "Category")]
public int? CategoryID { get; set; }
[Display(Name = "Department")]
public int DepartmentID { get; set; }
[Display(Name = "Page Size")]
public int? PageSize { get; set; }
public List<Category> Categories { get; set; }
public List<Department> Departments { get; set; }
}
public class ProductPageViewModel
{
public ProductPageViewModel()
{
ProductPageCriteria = new ProductPageCriteriaViewModel();
Products = new List<Product>();
}
public ProductPageCriteriaViewModel ProductPageCriteria { get; set; }
public List<Product> Products { get; set; }
}
public class Product
{
public int ProductID { get; set; }
public string ProductName { get; set; }
public Category Category { get; set; }
public Department Department { get; set; }
}
public class Category
{
public int CategoryID { get; set; }
public string CategoryName { get; set; }
}
public class Department
{
public int DepartmentID { get; set; }
public string DepartmentName { get; set; }
}
View Index.cshtml
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
#Html.Partial("_ProductCriteria", Model.ProductPageCriteria)
#Html.Partial("_ProductList", Model.Products)
}
Partial View _ProductCriteria.cshtml
#model Mvc3Application4.Models.ProductPageCriteriaViewModel
<fieldset>
<legend>Criteria</legend>
<div class="editor-label">
#Html.LabelFor(model => model.CategoryID)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.CategoryID, new SelectList(Model.Categories, "CategoryID", "CategoryName", Model.CategoryID), "--- All ---")
#Html.ValidationMessageFor(model => model.CategoryID)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.DepartmentID)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.DepartmentID, new SelectList(Model.Departments, "DepartmentID", "DepartmentName", Model.DepartmentID), "--- All ---")
#Html.ValidationMessageFor(model => model.DepartmentID)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.PageSize)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.PageSize, new SelectList(new List<int> {10, 15, 20, 25, 50, 100}.Select(n => new {Value = n, Text = n}), "Value", "Text", Model.PageSize), "--- All ---")
#Html.ValidationMessageFor(model => model.PageSize)
</div>
<p>
<input type="submit" value="Search" />
</p>
</fieldset>
Partial View _ProductList.cshtml
#model IEnumerable<Mvc3Application4.Models.Product>
<p>
#Html.ActionLink("Create New", "Create")
</p>
<table>
<tr>
<th></th>
<th>
ProductName
</th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.ActionLink("Edit", "Edit", new { id=item.ProductID }) |
#Html.ActionLink("Details", "Details", new { id=item.ProductID }) |
#Html.ActionLink("Delete", "Delete", new { id=item.ProductID })
</td>
<td>
#item.ProductName
</td>
</tr>
}
</table>
This is off the top of my head and untested, but I believe if you pass the parent model (ProductPageViewModel) to the products criteria partial view, change the partial view to inherit this model, and change the controls to use from model => model.ProductPageCriteria.CategoryID instead of model => model.CategoryID, it should maintain the naming so that UpdateModel can match up the fields with the posted values.
Sorry for the extreme run-on sentence and if this is incorrect I'm sure I'll earn my Peer Pressure badge pretty quickly. :) Hope this helps.

Resources