ASP.Net MVC: How to associate hobbies with each student - asp.net-mvc

I am facing trouble to create association between student and hobbies. i am showing my data though editable webgrid. webgrid has textboxes for name, dropdown for country selection and checkboxes for hobbies.
i want when user select each student hobbies...may be one or multiple and press submit button then i should be able to know each student's hobbies from student view model.
due to lack of knowledge i am not being able to do it.
this how my UI looks
This way i am generation checkboxes in each row webgrid.
grid.Column(header: "Hobbies",
format: #<text>
#for (var i = 0; i < Model.Hobbies.Count; i++)
{
<div class="checkbox">
#Html.HiddenFor(m => m.Hobbies[i].ID)
#Html.HiddenFor(m => m.Hobbies[i].Name)
#Html.CheckBoxFor(m => m.Hobbies[i].Checked)
#Html.LabelFor(m =>m.Hobbies[i].ID, Model.Hobbies[i].Name)
</div>
}
</text>)
my full razor code
#model MVCCRUDPageList.Models.StudentListViewModel
#{
ViewBag.Title = "Index";
}
<h2>Student View Model</h2>
#using (Html.BeginForm("Index", "WebGridMoreControls", FormMethod.Post))
{
var grid = new WebGrid(Model.Students, canSort: false, canPage: false);
var rowNum = 0;
var SelectedHobbies = 0;
<div id="gridContent" style=" padding:20px; ">
#grid.GetHtml(
tableStyle: "table",
alternatingRowStyle: "alternate",
selectedRowStyle: "selected",
headerStyle: "header",
columns: grid.Columns
(
grid.Column(null, header: "Row No", format: item => rowNum = rowNum + 1),
grid.Column("ID", format: (item) => #Html.TextBoxFor(m => m.Students[rowNum - 1].ID, new { #class = "edit-mode" })),
grid.Column("Name", format: (item) => #Html.TextBoxFor(m => m.Students[rowNum - 1].Name, new { #class = "edit-mode" })),
grid.Column("Country", format: (item) =>
#Html.DropDownListFor(x => x.Students[rowNum - 1].CountryID,
new SelectList(Model.Country, "ID", "Name", item.CountryID),
"-- Select Countries--", new { id = "cboCountry", #class = "edit-mode" })),
grid.Column(header: "Hobbies",
format: #<text>
#for (var i = 0; i < Model.Hobbies.Count; i++)
{
<div class="checkbox">
#Html.HiddenFor(m => m.Hobbies[i].ID)
#Html.HiddenFor(m => m.Hobbies[i].Name)
#Html.CheckBoxFor(m => m.Hobbies[i].Checked)
#Html.LabelFor(m =>m.Hobbies[i].ID, Model.Hobbies[i].Name)
</div>
}
</text>)
))
<input type="submit" value="Submit" />
</div>
}
My controller and action code
public class WebGridMoreControlsController : Controller
{
// GET: WebGridMoreControls
public ActionResult Index()
{
StudentListViewModel osvm = new StudentListViewModel();
return View(osvm);
}
[HttpPost]
public ActionResult Index(StudentListViewModel oStudentListViewModel)
{
return View(oStudentListViewModel);
}
}
My ViewModel code
public class StudentListViewModel
{
public IList<Student> Students { get; set; }
public List<Country> Country { get; set; }
public IList<Hobby> Hobbies { get; set; }
public StudentListViewModel()
{
Students = new List<Student>
{
new Student{ID=1,Name="Keith",CountryID=0},
new Student{ID=2,Name="Paul",CountryID=2},
new Student{ID=3,Name="Sam",CountryID=3}
};
Country = new List<Country>
{
new Country{ID=1,Name="India"},
new Country{ID=2,Name="UK"},
new Country{ID=3,Name="USA"}
};
Hobbies = new List<Hobby>
{
new Hobby{ID=1,Name="Football",Checked=false},
new Hobby{ID=2,Name="Hocky",Checked=false},
new Hobby{ID=3,Name="Cricket",Checked=false}
};
}
}
My Model code
public class Student
{
public int ID { get; set; }
[Required(ErrorMessage = "First Name Required")]
public string Name { get; set; }
//[Required(ErrorMessage = "Last Name Required")]
//public string LastName { get; set; }
public int CountryID { get; set; }
}
public class Country
{
public int ID { get; set; }
public string Name { get; set; }
}
public class Hobby
{
public int ID { get; set; }
public string Name { get; set; }
public bool Checked { get; set; }
}
please help me what i am trying to achieve. thanks
EDIT 1
#for (var i = 0; i < Model.Hobbies.Count; i++)
{
<div class="checkbox">
#Html.HiddenFor(m => m.Hobbies[i].ID)
#Html.HiddenFor(m => m.Hobbies[i].Name)
#Html.HiddenFor(m => m.Students[rowNum - 1].Hobbies[i].ID)
#Html.CheckBoxFor(m => m.Students[rowNum - 1].Hobbies[i].Checked)
#*#Html.CheckBoxFor(m => m.Hobbies[i].Checked)*#
#Html.LabelFor(m =>m.Hobbies[i].ID, Model.Hobbies[i].Name)
</div>
}

I could restructure my view model,model and razor view code. here i like to give my updated code which is working fine.
This way i generate checkboxes in forloop.
grid.Column(header: "Hobbies",
format: #<text>
#for (var i = 0; i < Model.Students.FirstOrDefault().Hobbies.Count; i++)
{
<div class="checkbox">
#Html.HiddenFor(m => m.Students[rowNum - 1].Hobbies[i].ID)
#Html.HiddenFor(m => m.Students[rowNum - 1].Hobbies[i].Name)
#Html.CheckBoxFor(m => m.Students[rowNum - 1].Hobbies[i].Checked)
#Html.LabelFor(m => m.Students[rowNum - 1].Hobbies[i].Name, Model.Students.FirstOrDefault().Hobbies[i].Name)
</div>
}
</text>)
view model and model class code
public class StudentListViewModel
{
public IList<Student> Students { get; set; }
public List<Country> Country { get; set; }
public StudentListViewModel()
{
Students = new List<Student>
{
new Student
{
ID=1,Name="Keith",CountryID=0,
Hobbies= new List<Hobby>
{
new Hobby{ID=1,Name="Football",Checked=false},
new Hobby{ID=2,Name="Hocky",Checked=false},
new Hobby{ID=3,Name="Cricket",Checked=false}
}
},
new Student
{
ID=2,Name="Paul",CountryID=2,
Hobbies= new List<Hobby>
{
new Hobby{ID=1,Name="Football",Checked=false},
new Hobby{ID=2,Name="Hocky",Checked=false},
new Hobby{ID=3,Name="Cricket",Checked=false}
}
},
new Student
{
ID=3,Name="Sam",CountryID=3,
Hobbies= new List<Hobby>
{
new Hobby{ID=1,Name="Football",Checked=false},
new Hobby{ID=2,Name="Hocky",Checked=false},
new Hobby{ID=3,Name="Cricket",Checked=false}
}
}
};
Country = new List<Country>
{
new Country{ID=1,Name="India"},
new Country{ID=2,Name="UK"},
new Country{ID=3,Name="USA"}
};
}
}
Model code
public class Student
{
public int ID { get; set; }
[Required(ErrorMessage = "First Name Required")]
public string Name { get; set; }
//[Required(ErrorMessage = "Last Name Required")]
//public string LastName { get; set; }
public int CountryID { get; set; }
public IList<Hobby> Hobbies { get; set; }
}
public class Country
{
public int ID { get; set; }
public string Name { get; set; }
}
public class Hobby
{
public int ID { get; set; }
public string Name { get; set; }
public bool Checked { get; set; }
}
Bit Different way done again
The same UI i also developed with html table. here i am sharing the razor code and rest of the model and model view code is same as before.
#model MVCCRUDPageList.Models.StudentListViewModel
#{
ViewBag.Title = "Index";
}
<h2>CREATE TABULAR UI WITH HTML TABLE</h2>
#using (Html.BeginForm("Index", "HtmlTable", FormMethod.Post))
{
<div class="form-group">
<div class="col-md-12 table-responsive">
<table class="table table-bordered table-hover">
<tr>
<th>
Row No
</th>
<th>
ID
</th>
<th>
Name
</th>
<th>
Country
</th>
<th>
Hobbies
</th>
<th>
Sex
</th>
</tr>
}
#for (int x=0; x<=Model.Students.Count-1;x++)
{
<tr>
<td>
<label>#(x+1)</label>
</td>
<td>
#Html.TextBoxFor(m => m.Students[x].ID)
</td>
<td>
#Html.TextBoxFor(m => m.Students[x].Name)
</td>
<td>
#Html.DropDownListFor(m => m.Students[x].CountryID,
new SelectList(Model.Country, "ID", "Name", Model.Students[x].CountryID),
"-- Select Countries--", new { id = "cboCountry", #class = "edit-mode" })
</td>
<td>
#for (var i = 0; i < Model.Students.FirstOrDefault().Hobbies.Count; i++)
{
<div class="checkbox">
#Html.HiddenFor(m => m.Students[x].Hobbies[i].ID)
#Html.HiddenFor(m => m.Students[x].Hobbies[i].Name)
#Html.CheckBoxFor(m => m.Students[x].Hobbies[i].Checked)
#Html.LabelFor(m => m.Students[x].Hobbies[i].Name, Model.Students[x].Hobbies[i].Name)
</div>
}
</td>
<td>
#for (var i = 0; i < Model.Sex.Count; i++)
{
<div class="checkbox">
#Html.HiddenFor(m => Model.Sex[i].ID)
#Html.HiddenFor(m => Model.Sex[i].SexName)
#Html.RadioButtonFor(m => m.Students[x].SexID, Model.Sex[i].ID)
#Html.LabelFor(m => m.Students[x].SexID, Model.Sex[i].SexName)
</div>
}
</td>
</tr>
}
</table>
</div>
<input type="submit" value="Submit" />
</div>
}
[1]: https://stackoverflow.com/users/3559349/stephen-muecke

Related

Get Null when Binding List of Object to Controller MVC Model

I am having trouble binding a model that contains list of objects for Editing method. This is the list of Factory which includes list of another object (FactoryHotline).
There is no problem when I get pass data from Controller to View. But when I try to send data from View back to Controller, some model's properties always null.
The Model is:
public class Factory
{
public Guid Id { get; set; }
public string Name { get; set; }
public List<FactoryHotline> FactoryHotlineList { get; set; }
}
public class FactoryHotline
{
public Guid Id { get; set; }
public Guid FactoryId { get; set; }
public string Caption { get; set; }
public string Hotline { get; set; }
}
This is View:
#model List<WebDataLayer.Models.Factory>
<form action="/Factories/Edit" method="POST" enctype="multipart/form-data">
#Html.AntiForgeryToken()
<div class="form-horizontal">
<table id="factoriesTable">
<thead>
<tr>
<th>Name</th>
<th class="Hotline1" >Hotline 1</th>
<th class="Hotline2" >Hotline 2</th>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.Count; i++)
{
#Html.HiddenFor(model => model[i].Id)
<tr>
<td>#Model[i].Name</td>
#for (int h = 0; h < Model[i].FactoryHotlineList.Count; h++)
{
<td>
<div>
<b>Caption: </b>
#Html.EditorFor(model => model[i].FactoryHotlineList[h].Caption, new { htmlAttributes = new { #class = "form-control ShortInput", id = "captionInput", maxlength = "39" } })
</div>
<div>
<b>Hotline:</b>
#Html.EditorFor(model => model[i].FactoryHotlineList[h].Hotline, new { htmlAttributes = new { #class = "form-control ShortInput", id = "hotlineInput", maxlength = "15" } })
#Html.ValidationMessageFor(model => model[i].FactoryHotlineList[h].Hotline)
</div>
</td>
}
</tr>
}
</tbody>
</table>
</form>
In my controller the method for Edit is:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit (List<Factory> factories)
{
}
Only Id has value, the other(Caption, Hotline) are always null in List<Factory> factories
This is how I am passing Data from Controller to View
// GET: Edit
public ActionResult Edit()
{
var factories = _factoryService.All().OrderBy(p => p.Name);
var list = factories.ToList();
return View("Edit", list);
}
I works fine using Entity Framework.
That is because you have used HiddenFor to keep id as hidden field. To have the value in postback, it should be a part of input element(input,select,checkbox,textarea,etc) or as hidden field.
#Html.HiddenFor(model => model[i].Name)
I would suggest using a viewmodel along with automapper in this case.

Mvc 5 - 2 model in view

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

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>

Model not retaining value in MVC3 on Post

I'm working a viewmodel (vm) for creating a new wine. I assign the ProducerID value to the vm on the get based on the user's profile. I can see the ProducerID value in the view when it is rendered in the view. The user cannot choose or edit this value unless they are in the admin role (i'm not testing with that role). My issue is the ProducerID always comes back on the POST as 0. I don't know what I'm missing as my other selected options in the view come back fine.
I've tried to put a new unique name in the vm itself, but that didn't hold a value either. I've look around and found some other people with similar issues, but none of their solutions have helped. Any assistance on this would be awesome. Thanks!
viewmodel:
{
public Wine Wine { get; set; }
public VOAVIRequest VOAVIRequest { get; set; }
public bool IsRequest { get; set; }
public SelectList VarTypes { get; set; }
public SelectList Origins { get; set; }
public SelectList Apps { get; set; }
public SelectList Vintages { get; set; }
public SelectList Importers { get; set; }
public NewWineViewModel()
{
this.Wine = new Wine();
}
}
wine model:
public class Wine :Updater
{
public int WineID { get; set; }
//public int WineTypeID { get; set; }
[Display(Name = "Varietal/Type")]
public int VarTypeID { get; set; }
[Display(Name = "Origin")]
public int OriginID { get; set; }
[Display(Name = "Appellation")]
public int AppID { get; set; }
[Display(Name = "Vintage")]
public int VintageID { get; set; }
[Display(Name = "Importer")]
public int? ImporterID { get; set; }
public int ProducerID { get; set; }
public string Designate { get; set; }
[Display(Name = "Drink Window")]
public string DrinkWindow { get; set; }
public string Body { get; set; }
public string SKU { get; set; }
[Display(Name = "Case Production")]
public double CaseProduction { get; set; }
[Display(Name = "Alcohol Content")]
public double AlcoholContent { get; set; }
public string Winemaker { get; set; }
[Display(Name = "Consulting Winemaker")]
public string ConsultWinemaker { get; set; }
public bool Sustainable { get; set; }
public bool Kosher { get; set; }
public bool Organic { get; set; }
public bool Biodynamic { get; set; }
public bool SalmonSafe { get; set; }
public Boolean Active { get; set; }
public virtual WineType WineType { get; set; }
public virtual VarType VarType { get; set; }
public virtual Origin Origin { get; set; }
public virtual App App { get; set; }
public virtual Vintage Vintage { get; set; }
public virtual Importer Importer { get; set; }
public virtual Producer Producer { get; set; }
public virtual ICollection<Review> Reviews { get; set; }
public virtual ICollection<Doc> Docs { get; set; }
public IEnumerable<SelectListItem> BodyList { get; set; }
//for dropdownlist binding
//public IEnumerable<VarType> VarTypes { get; set; }
//public IEnumerable<Origin> Origins { get; set; }
//public IEnumerable<App> Apps { get; set; }
//public IEnumerable<Vintage> Vintages { get; set; }
//public IEnumerable<Importer> Importers { get; set; }
//public IEnumerable<Producer> Producers { get; set; }
public Wine()
{
var BodyList = new List<SelectListItem>()
{
new SelectListItem {Value="", Text="Please select wine body"},
new SelectListItem {Value="", Text="Light-bodied"},
new SelectListItem {Value="", Text="Light to Medium-bodied"},
new SelectListItem {Value="", Text="Medium-bodied"},
new SelectListItem {Value="", Text="Medium to Full-bodied"},
new SelectListItem {Value="", Text="Full-bodied"},
new SelectListItem {Value="", Text="Very Full-bodied"}
};
this.BodyList = BodyList;
}
public virtual String Name {
get {
string sName = string.Empty;
int iVintage;
if (!int.TryParse(this.Vintage.Name.Trim(), out iVintage))
{
sName = iVintage.ToString();
}
if (!string.IsNullOrEmpty(this.Designate))
{
sName = sName + " " + this.Producer.Name + " " + this.Designate + " " + this.VarType.Name;
}
else
{
sName = sName + " " + this.Producer.Name + " " + this.VarType.Name;
}
return sName;
}
}
}
controller:
public ActionResult Create()
{
NewWineViewModel nw = new NewWineViewModel();
nw.VarTypes = new SelectList(db.VarTypes, "VarTypeID", "Name").Default("Select a Varietal/Type", "0");
nw.Origins = new SelectList(db.Origins, "OriginID", "Name").Default("Select an Origin", "0");
nw.Apps = new SelectList(db.Apps, "AppID", "Name").Default("Select an Appellation", "0");
nw.Vintages = new SelectList(db.Vintages, "VintageID", "Name").Default("Select a Vintage", "0");
nw.Importers = new SelectList(db.Importers, "ImporterID", "Name").Default("Select an Importer", "0");
// keep dynamic
if (User.IsInRole("producer"))
{
Producer currentProd = db.ProducerUsers.Find(Membership.GetUser().ProviderUserKey).Producer;
nw.Wine.ProducerID = currentProd.ProducerID;
ViewBag.ProducerName = currentProd.Name;
ViewBag.ProducerID = currentProd.ProducerID;
}
else
{
ViewBag.ProducerSelect = new SelectList(db.Producers, "ProducerID", "Name");
}
ViewData.Model = nw;
return View();
}
//
// POST: /Wine/Create
[HttpPost]
//[Authorize(Roles = "admin, producereditor")]
public ActionResult Create(NewWineViewModel nw)
{
if (ModelState.IsValid)
{
nw.Wine.Active = nw.IsRequest ? false : true;
nw.Wine.ImporterID = nw.Wine.ImporterID == 0 ? null : nw.Wine.ImporterID;
nw.Wine.CreatedBy = this.User.Identity.Name;
nw.Wine.CreatedOn = DateTime.Now;
db.Wines.Add(nw.Wine);
db.SaveChanges();
if (nw.IsRequest)
{
nw.VOAVIRequest.WineID = nw.Wine.WineID;
db.VOAVIRequests.Add(nw.VOAVIRequest);
RedirectToAction("Requested");
//redirect to "Request Submitted" page for new wines
}
return RedirectToAction("Details", nw.Wine.WineID);
}
ViewBag.VarTypeID = new SelectList(db.VarTypes, "VarTypeID", "Name").Default("Select a Varietal/Type", nw.Wine.VarTypeID.ToString());
ViewBag.OriginID = new SelectList(db.Origins, "OriginID", "Name").Default("Select an Origin", nw.Wine.OriginID.ToString());
ViewBag.AppID = new SelectList(db.Apps, "AppID", "Name").Default("Select an Appellation", nw.Wine.AppID.ToString());
ViewBag.VintageID = new SelectList(db.Vintages, "VintageID", "Name").Default("Select a Vintage", nw.Wine.VintageID.ToString());
ViewBag.ImporterID = new SelectList(db.Importers, "ImporterID", "Name").Default("Select an Importer", nw.Wine.ImporterID.ToString());
if (User.IsInRole("producer"))
{
Producer currentProd = db.ProducerUsers.Find(Membership.GetUser().ProviderUserKey).Producer;
ViewBag.ProducerID = currentProd.ProducerID;
ViewBag.ProducerName = currentProd.Name;
}
else
{
ViewBag.ProducerSelect = new SelectList(db.Producers, "ProducerID", "Name" ,nw.Wine.ProducerID);
}
return View(nw);
}
view:
#model vf2.ViewModels.NewWineViewModel
#{
ViewBag.Title = "Create a Wine";
}
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
if (User.IsInRole("admin"))
{
<div class="editor-label">
#Html.LabelFor(m => m.Wine.ProducerID, "Producer")
</div>
<div class="editor-field">
#Html.DropDownListFor(m => m.Wine.ProducerID, ViewBag.ProducerSelect as SelectList, "Select a Varietal/Type")
#*#Html.DropDownList("ProducerSelect", String.Empty)*#
</div>
}
else
{
<h3>#ViewBag.ProducerName</h3>
}
#Html.HiddenFor(m => m.IsRequest)
<table>
<tr>
<td>#Html.LabelFor(m => m.Wine.VarTypeID, "VarType")
</td>
<td>
<div class="voavi-select">
#Html.DropDownListFor(m => m.Wine.VarTypeID, Model.VarTypes, new { #class = "chzn-select" })
</div>
#Html.TextBoxFor(m => m.VOAVIRequest.VarType, new { style = "display: none;", #class = "voavignore" })
<a id="lnkNewVar" class="filetypes" href="#">New Varietal?</a> #* #Html.ValidationMessageFor(m => m.VOAVIRequest.VarType)*#
</td>
</tr>
<tr>
<td>
#Html.LabelFor(m => m.Wine.OriginID, "Origin")
</td>
<td>
<div class="voavi-select">
#Html.DropDownListFor(m => m.Wine.OriginID, Model.Origins, new { #class = "chzn-select" })
</div>
<a id="lnkNewOrigin" class="filetypes" href="#">New Origin?</a>
#Html.TextBoxFor(m => m.VOAVIRequest.Origin, new { style = "display: none;", #class = "voavignore" })
</td>
</tr>
<tr>
<td>
#Html.LabelFor(m => m.Wine.AppID, "App")
</td>
<td>
<div class="voavi-select">
#Html.DropDownListFor(m => m.Wine.AppID, Model.Apps, new { #class = "chzn-select" })
</div>
<a id="lnkNewApp" class="filetypes" href="#">New Varietal?</a>
#Html.TextBoxFor(m => m.VOAVIRequest.App, new { style = "display: none;", #class = "voavignore" })
</td>
</tr>
<tr>
<td>
#Html.LabelFor(m => m.Wine.VintageID, "Vintage")
</td>
<td>
<div class="voavi-select">
#Html.DropDownListFor(m => m.Wine.VintageID, Model.Vintages, new { #class = "chzn-select" })
</div>
<a id="lnkNewVintage" class="filetypes" href="#">New Varietal?</a>
#Html.TextBoxFor(m => m.VOAVIRequest.Vintage, new { style = "display: none;", #class = "voavignore" })
</td>
</tr>
<tr>
<td>
#Html.LabelFor(m => m.Wine.Designate)
</td>
<td>
#Html.EditorFor(m => m.Wine.Designate)
</td>
</tr>
<tr>
<td>
#Html.LabelFor(m => m.Wine.DrinkWindow)
</td>
<td>
#Html.EditorFor(m => m.Wine.DrinkWindow)
</td>
</tr>
<tr>
<td>
#Html.LabelFor(m => m.Wine.Body)
</td>
<td>
#Html.DropDownListFor(m => m.Wine.Body, new SelectList(Model.Wine.BodyList, "Value", "Text"), new { #class = "chzn-select" })
</td>
</tr>
<tr>
<td>
#Html.LabelFor(m => m.Wine.ImporterID, "Importer")
</td>
<td>
<div class="voavi-select">
#Html.DropDownListFor(m => m.Wine.ImporterID, Model.Importers, new { #class = "chzn-select" })</div>
<a id="lnkNewImporter" class="filetypes" href="#">New Varietal?</a>
#Html.TextBoxFor(m => m.VOAVIRequest.Importer, new { style = "display: none;" })
</td>
</tr>
<tr>
<td>
#Html.LabelFor(m => m.Wine.SKU)
</td>
<td>
#Html.EditorFor(m => m.Wine.SKU)
</td>
</tr>
<tr>
<td>
#Html.LabelFor(m => m.Wine.CaseProduction)
</td>
<td>
#Html.EditorFor(m => m.Wine.CaseProduction)
</td>
</tr>
<tr>
<td>
#Html.LabelFor(m => m.Wine.AlcoholContent)
</td>
<td>
#Html.EditorFor(m => m.Wine.AlcoholContent)
</td>
</tr>
<tr>
<td>
#Html.LabelFor(m => m.Wine.Winemaker)
</td>
<td>
#Html.EditorFor(m => m.Wine.Winemaker)
</td>
</tr>
<tr>
<td>
#Html.LabelFor(m => m.Wine.ConsultWinemaker)
</td>
<td>
#Html.EditorFor(m => m.Wine.ConsultWinemaker)
</td>
</tr>
<tr>
<td>
#Html.LabelFor(m => m.Wine.Sustainable)
</td>
<td>
#Html.EditorFor(m => m.Wine.Sustainable)
</td>
</tr>
<tr>
<td>
#Html.LabelFor(m => m.Wine.Kosher)
</td>
<td>
#Html.EditorFor(m => m.Wine.Kosher)
</td>
</tr>
<tr>
<td>
#Html.LabelFor(m => m.Wine.Organic)
</td>
<td>
#Html.EditorFor(m => m.Wine.Organic)
</td>
</tr>
<tr>
<td>
#Html.LabelFor(m => m.Wine.Biodynamic)
</td>
<td>
#Html.EditorFor(m => m.Wine.Biodynamic)
</td>
</tr>
<tr>
<td>
#Html.LabelFor(m => m.Wine.SalmonSafe)
</td>
<td>
#Html.EditorFor(m => m.Wine.SalmonSafe)
</td>
</tr>
</table>
<p>
<input type="submit" value="Create" />
</p>
}
ProducerID isn't being populated because it looks like it's not being posted back with the form. If it's not part of your route, you need to persist it in a hidden field:
#Html.HiddenFor(m => m.ProducerID)

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