View is expecting for viewmodel while List item has been passed to controller - asp.net-mvc

Error::
The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[ProjDAL.Models.ViewModels.EmpViewModel]', but this dictionary requires a model item of type 'ProjDAL.Models.ViewModels.EmpViewModel'.
I am working on a mvc application. For one view which basically has a functionality for multiple parameter search. I am using same view for get and post method... It means i am passing 1 or more parameters to the textboxes and fetching the result using linq and mapping to a datatable.
After passing the parameters, the values are going to the controller and fetching the exact result using linq but the problem is coming when i am trying to map the linq result-set to my view.
Here is the code for the project -
Controller -
[HttpPost]
public ActionResult EmpSearch([Bind(Include = "employee,EmpID,PAN")] Get_EmpDetails_Result get_EmpDetails_Result)
{
var result = from emp in dbCollections.Employees
join nat in dbCollections.NationalID on emp.EmpID equals nat.EmpID
select new EmpViewModel
{
PAN = nat.pan,
EmpID = emp.EmpID,
employee = emp.Name
};
var searchRes = result
.Where(s => s.PAN.Contains(get_EmpDetails_Result.pan)
|| s.EmpID.Contains(get_EmpDetails_Result.empid)
|| s.employee.Contains(get_EmpDetails_Result.employee));
var modelSys = searchRes.ToList();
return View(modelSys);
}
View ::::
#model NewProjDAL.Models.ViewModels.EmpViewModel
#{
ViewBag.Title = "empDetails";
Layout = "~/Views/Shared/_Layout.cshtml";
}
//////////// this part is for the multiple criteria search
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
<label>By EmpID</label>
<div class="col-md-10">
#Html.EditorFor(model => model.GetEmpDetails.FirstOrDefault().empid, new { htmlAttributes = new { #class = "form-control" } })
</div>
</div>
<div class="form-group">
<label>By PAN</label>
<div class="col-md-10">
#Html.EditorFor(model => model.GetEmpDetails.FirstOrDefault().pan, new { htmlAttributes = new { #class = "form-control" } })
</div>
</div>
<div class="form-group">
<label>By Name</label>
<div class="col-md-10">
#Html.EditorFor(model => model.GetEmpDetails.FirstOrDefault().employee, new { htmlAttributes = new { #class = "form-control" } })
</div>
</div>
<div class="form-actions center">
<button type="submit" data-animation="pulse" class="btn btn-lg font-medium-1 btn-outline-teal mb-1 block-multiple-msgs buttonAnimation">
<i class="fa fa-check-square-o"></i> Go
</button>
</div>
</div>
}
//////////////////////////////this part is to fetch the result of the previously mentioned multiple search
#if (Model.EmpDetails.Count != 0)
{
<div class="table-responsive">
<table class="table table-striped table-bordered dom-jQuery-events compact" id="DataTbl">
<thead class="navbar-dark navbar-dark bg-blue-grey white">
<tr>
<th>
employee
</th>
<th>
PAN
</th>
<th>
empid
</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.GetEmpDetails)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.employee)
</td>
<td>
#Html.DisplayFor(modelItem => item.PAN)
</td>
<td>
#Html.DisplayFor(modelItem => item.empid)
</td>
</tr>
}
</tbody>
</table>
</div>
}

It's told you exactly what the problem is. You've set up the view to accept a single EmpViewModel as the Model, but your controller is passing a List
You need to create a view model to represent the search criteria and the results
public class EmployeeSearchViewModel
{
public string EmpId { get; set; }
public string PAN { get; set; }
public string Employee { get; set; }
public List<EmpViewModel> Employees { get; set; } = new List<EmpViewModel>();
}
then in your controller method:
var modelSys = searchRes.ToList();
var viewModel = new EmployeeSearchViewModel
{
PAN = get_EmpDetails_Result.pan,
EmpId = get_EmpDetails_Result.empid,
Employee = get_EmpDetails_Result.employee
Employees = modelSys
};
return View(viewModel);
In the view:
#Model EmployeeSearchViewModel
Then your parameters display don't need to pull the EmpID, PAN, etc. from the collection, just from the view model, and you can bind your repeated results to the inner collection "Employees".

Related

ASP.NET MVC Send List from View to controller

I'm trying to create a product model with ID,Name and a list of specifications like above:
My model:
public class Product
{
public int ProductID { get; set; }
public string Name { get; set; }
public virtual List<Spec> Specifications { get; set; }
}
public class Spec
{
public int SpecID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
My Controller:
public ActionResult Create(Product product,List<Spec> Specifications)
{
......
}
My View:
using (Html.BeginForm("Create", "Products", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Product</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-2"><h3>Specifications</h3></div>
<div class="col-md-10">
<table id="tblSkills" cellpadding="0" cellspacing="0" class="table table-responsive">
<thead>
<tr>
<th style="width:150px">Name</th>
<th style="width:150px">Description</th>
<th></th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td><input type="text" id="Name1" /></td>
<td><input type="text" id="Description" /></td>
<td>
<input type="button" id="btnAdd" class="btn btn-success btn-sm" value="Add" />
</td>
</tr>
</tfoot>
</table>
<br />
<input type="button" id="btnSave" value="SaveAll" class="bntbtn-block btn-success" />
<br />
</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>
}
so it should looks like this:
I also added some Scripts so that I can enter or remove specifications, the information will be displayed inside a tbody tag in a table.
The problem is that I don't really know how to pass my list of specifications to my controller, or should I try another way of input multiple specifications instead of using table. I'm looking for a way to input it using HTMLHelper like the one I did with Product's Name.
I apologize if my question is unclear. If you have any question to understand more, feel free to ask me. Thanks for any advise or solution.
To pass the model to a view from controller you need to:
public ActionResult Create(List<Spec> Specifications)
{
return View(Specifications);
}
and in your view add these to on top of the view:
#using PathOfYourSpecificationsModel
#model List<Spec>
using (Html.BeginForm("Create", "Products", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Product</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-2"><h3>Specifications</h3></div>
<div class="col-md-10">
<table id="tblSkills" cellpadding="0" cellspacing="0" class="table table-responsive">
<thead>
<tr>
<th style="width:150px">Name</th>
<th style="width:150px">Description</th>
<th></th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td><input type="text" id="Name1" /></td>
<td><input type="text" id="Description" /></td>
<td>
<input type="button" id="btnAdd" class="btn btn-success btn-sm" value="Add" />
</td>
</tr>
</tfoot>
</table>
<br />
<input type="button" id="btnSave" value="SaveAll" class="bntbtn-block btn-success" />
<br />
</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>
}
After user clicks add button you need another [HttpPost] method for Create. Which should look like this:
[HttpPost]
public ActionResult Create(List<Spec> Specifications)
{
// Specifications should be filled with view values.
// Do your logic here. Ex: Save the data to database
}
For adding dynamic control fields, it is advised to use helper methods.
The AddNewRow helper method will return the html elements can one can make changes like changing the html attributes.
the html attributes should be unique and it is advised to use increment value for each element.
the attributes of html elements returned from helper method are changed in addNewRow() of javascript function.
Detailed steps are provided below.
In Product Model
public class Product
{
public int ProductID { get; set; }
public string Name { get; set; }
public List<Spec> Specifications { get; set; }
}
public class Spec
{
public int SpecID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool IsRemoved { get; set; }
}
In Controller
public class ProductController : Controller
{
// GET: Product
public ActionResult AddProduct()
{
Product product = new Product();
product.Specifications = new List<Spec>()
{
new Spec()
};
return View(product);
}
[HttpPost]
public ActionResult AddProduct(Product product)
{
return View(product);
}
}
In AddProduct.cshtml
#model Product
#using WebApplication3.Models
#{
ViewBag.Title = "AddProduct";
}
#helper AddNewRow()
{
<tr id="trRow_0">
<td>
#Html.HiddenFor(x => Model.Specifications[0].IsRemoved, new { id = "hdnSpecIsRemoved_0" })
#Html.TextBoxFor(x => Model.Specifications[0].Name, new { id = "txtSpecName_0" })
</td>
<td>
#Html.TextBoxFor(x => Model.Specifications[0].Description, new { id = "txtSpecDesc_0" })
</td>
<td>
Remove Row
</td>
</tr>
}
<h2>AddProduct</h2>
#using (Html.BeginForm("AddProduct", "Product", FormMethod.Post))
{
<div>
#Html.LabelFor(x => x.Name)
#Html.TextBoxFor(x => x.Name)
</div>
<table>
<thead>
<tr>
<th>
#Html.LabelFor(x => x.Specifications[0].Name)
</th>
<th>
#Html.LabelFor(x => x.Specifications[0].Description)
</th>
<th>
Action
</th>
</tr>
</thead>
<tbody id="tBody">
#for (int i = 0; i < Model.Specifications.Count; i++)
{
string trRow = "trRow_" + i;
<tr id="#trRow">
<td>
#Html.HiddenFor(x => Model.Specifications[i].IsRemoved, new { id = "hdnSpecIsRemoved_" + i })
#Html.TextBoxFor(x => Model.Specifications[i].Name, new { id = "txtSpecName_" + i })
</td>
<td>
#Html.TextBoxFor(x => Model.Specifications[i].Description, new { id = "txtSpecDesc_" + i })
</td>
<td>
Remove Row
</td>
</tr>
}
</tbody>
<tfoot>
<tr>
<td colspan="2">
<br />
<button type="button" onclick="addNewRow()">Add New Row</button>
</td>
</tr>
</tfoot>
</table>
<br />
<button type="submit">Save All</button>
}
<script type="text/javascript">
function addNewRow() {
var totalSpecCount = $('#tBody tr').length;
var newRowData = `#(AddNewRow())`;
newRowData = newRowData.replaceAll("Specifications[0]", "Specifications[" + totalSpecCount + "]")
newRowData = newRowData.replaceAll("txtSpecName_0", "txtSpecName_" + totalSpecCount);
newRowData = newRowData.replaceAll("txtSpecDesc_0", "txtSpecDesc_" + totalSpecCount);
newRowData = newRowData.replaceAll("trRow_0", "trRow_" + totalSpecCount);
newRowData = newRowData.replaceAll("removeRow(0)", "removeRow(" + totalSpecCount+")");
newRowData = newRowData.replaceAll("hdnSpecIsRemoved_0", "hdnSpecIsRemoved_" + totalSpecCount);
$('#tBody').append(newRowData);
}
function removeRow(recordId) {
var trId = "#trRow_" + recordId;
var hdnSpec = "#hdnSpecIsRemoved_" + recordId;
$(hdnSpec).val(true);
$(trId).hide();
}
</script>
Here, the method addNewRow will call the helper methods and change the html attributes of the element based on row count.
In strongly typed view, the index values should unique for the list so that it can be posted using model binding
Final Result
Note: In remove row method we have to hide the element instead of removing the element completely. This is used to achieve post the list directly. To know what the rows that are removed a flag called IsRemoved is to true.
If we remove the element, the index value will not be in sequence and one cannot post the form.

How to make an insert form and a table displaying data from database in a single view in MVC?

I'm making a CRUD but I want the create and read parts to be in a single MVC view. The create part is done, I've been trying to fill an HTML table with data from a database table when the view loads, but it won't let me do both things at once in a single view.
Here's the view header:
#model Console.Models.Product
#{
ViewBag.Title = "Create";
}
Here's the insert form:
#using (Html.BeginForm())
{
<div class="box-body">
<div class="form-horizontal">
#Html.AntiForgeryToken()
#Html.Hidden("productID", 0)
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
<div class="col-md-6">
#Html.EditorFor(model => model.productName, new { htmlAttributes = new { #class = "form-control", #name = "txtProductName", #id = "txtProductName" } })
</div>
#Html.ValidationMessageFor(model => model.productName, "", new { #class = "text-danger", Type = "productName" })
</div>
<div class="form-group">
<div class="col-md-6">
#Html.EditorFor(model => model.productQuantity, new { htmlAttributes = new { #class = "form-control", #name = "txtProductQuantity", #id = "txtProductQuantity" } })
</div>
#Html.ValidationMessageFor(model => model.productQuantity, "", new { #class = "text-danger" })
</div>
<div class="form-group">
<div class="col-md-6">
#Html.EditorFor(model => model.productColor, new { htmlAttributes = new { #class = "form-control", #name = "txtProductColor", #id = "txtProductColor" } })
</div>
#Html.ValidationMessageFor(model => model.productColor, "", new { #class = "text-danger" })
</div>
</div>
</div>
}
This is the table that should show the products that are inserted into the database in the form above:
<table id="Data_table" class="table table-bordered table-striped">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.productName)
</th>
<th>
#Html.DisplayNameFor(model => model.productQuantity)
</th>
<th>
#Html.DisplayNameFor(model => model.productColor)
</th>
<th>
</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.productName)
</td>
<td>
#Html.DisplayFor(modelItem => item.productQuantity)
</td>
<td>
#Html.DisplayFor(modelItem => item.productColor)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = item.productID }, new { #class = "btn btn-success btn-sm" })
#Html.ActionLink("Delete", "Delete", new { id = item.productID }, new { #class = "btn btn-danger btn-sm" })
</td>
</tr>
}
</tbody>
<tfoot>
<tr>
<th><div class="panel-footer">Total = #Model.Count()</div></th>
</tr>
</tfoot>
</table>
Problem is that I get an error at the foreach telling me to use IEnumerable with the model but whenever I do, the insert form gets an error. Is there any way to get around this?
Edit:
Here's the view model:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace Console.Models
{
public class ProductViewModel
{
public int productID{ get; set; }
public string productName{ get; set; }
public int productQuantity{ get; set; }
public string productColor{ get; set; }
public IEnumerable<Product> Products { get; set; }
}
}
In your view you do pass a single model, and then you try to iterate over it, that of course is not possible.
You should use view models. Create a view model that contains both the fields you need for the form and the list of products you want to iterate over on foreach.
#model ProductViewModel
#{
ViewBag.Title = "Create";
}
Create a view model, like this:
public class ProductViewModel {
public string ProductName { get; set; }
// Other fields for the form
public IEnumerable<Product> Products { get; set; } // Your list of products for the table
}
And then in your view:
#Html.EditorFor(model => model.ProductName,
// Continue with the form
#foreach (var item in Model.Products)
{ //...continue with your table

Multiple kendo grids in view using MVC Razor #foreach

Is it possible to create multiple kendo grids using MVC Razor #foreach?
I've tried the following and nothing is rendering in the view, it looks like the data is correct as I can see the correct JSON data in the script but no grid displays
#using Kendo.Mvc.UI
#model ProjectMVC.Models.IndexViewModel
#{
ViewData["Heading"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
#{
int gridIndex = 0;
foreach (var Heading in Model.Headings)
{
gridIndex++;
var Groups = Model.Groups.Where(x => x.Group == Heading);
tblGroups Group = Model.Groups.Where(x => x.Group == Heading).First();
#(Html.Kendo().Grid(Groups)
.Name($"grid{gridIndex}")
.Columns(columns =>
{
columns.Bound(c => c.Date).Width(140);
columns.Bound(c => c.User).Width(190);
columns.Bound(c => c.Category);
columns.Bound(c => c.Group).Width(110);
})
.Pageable()
.Filterable()
.Scrollable()
)
}
}
The following code does work using <\table> instead of Html.Kendo().Grid but I have been unable to recreate this using a kendo grid instead of tables. Can anyone point out where I might be going wrong?
Specifically this is aspnet core mvc.
MODEL:
public class tblGroups
{
[Key]
public int ID { get; set; }
public DateTime Date { get; set; }
public string User { get; set; }
public string Category { get; set; }
public string Group { get; set; } //<<Want separate tables split by this field
}
public class IndexViewModel
{
public List<tblGroups> Groups { get; set; }
public List<string> Headings { get; set; }
Public IndexViewModel()
{
Groups = new List<tblGroups>();
Headings = new List<string>();
}
}
VIEW:
#model MVC.Models.IndexViewModel
#{
ViewData["Heading"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
#{
foreach (var Heading in Model.Headings)
var Groups = Model.Groups.Where(x => x.Group == Heading);
tblGroups Group = Model.Groups.First(x => x.Prodcut == Heading);
<text>
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => Group.Date)
</th>
<th>
#Html.DisplayNameFor(model => Group.User)
</th>
<th>
#Html.DisplayNameFor(model => Group.Category)
</th>
<th>
#Html.DisplayNameFor(model => Group.Group)
</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Groups)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Date)
</td>
<td>
#Html.DisplayFor(modelItem => item.User)
</td>
<td>
#Html.DisplayFor(modelItem => item.Category)
</td>
<td>
#Html.DisplayFor(modelItem => item.Group)
</td>
<td>
</td>
</tr>
}
</tbody>
</table>
</text>
}
}
CONTROLLER:
public class tblGroupsController : Controller
{
public async Task<IActionResult> Index()
{
var allGroups = await _context.tblGroups.ToListAsync();
var Headings = allGroups.Select(x => x.Group).Distinct();
var model = new Models.IndexViewModel()
{
Groups = allGroups,
Headings = Headings
};
return View(model);
}
}
In order to use the same partial view multiple times, grid ID should be unique so passing the ID in partial view data is one possible solution. I have used the same grid multiple times in a PartialView by passing some parameters and retrieving different data via these parameters as shown below:
View:
<div class="row">
<div class="col-lg-6">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title" id="panel-title">New Issues</h3>
</div>
<div>
#Html.Partial("_List", new ViewDataDictionary { { "name", "grid-all-issues" }, { "style", "border:none; height:622px;" }, { "pageSize", "25" } })
</div>
</div>
</div>
<div class="col-lg-6">
<div class="row">
<div class="col-lg-12">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title" id="panel-title">Active Issues</h3>
</div>
<div>
#Html.Partial("_List", new ViewDataDictionary { { "name", "grid-waiting-issues" }, { "style", "border:none; height:273px;" }, { "pageSize", "10" } })
</div>
</div>
</div>
<div class="col-lg-12 top-10">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title" id="panel-title">Watched Issues</h3>
</div>
<div>
#Html.Partial("_List", new ViewDataDictionary { { "name", "grid-watched-issues" }, { "style", "border:none; height:273px;" }, { "pageSize", "10" } })
</div>
</div>
</div>
</div>
</div>
</div>
PartialView:
#(Html.Kendo().Grid<Models.ViewModel>()
.HtmlAttributes(new { style = #ViewData["style"].ToString() })
.Name(#ViewData["name"].ToString())
//...
.Columns(columns =>
{
columns.Bound(m => m.Key).Title("Issue No");
})
.DataSource(dataSource => dataSource
.Ajax()
.PageSize(Convert.ToInt32(#ViewData["pageSize"]))
)
)
Update: You can pass column parameter by using an approach below.
For passing parameter to a PartialView in ASP.NET MVC:
#Html.Partial("~/PathToYourView.cshtml", null, new ViewDataDictionary { { "VariableName", "some value" } })
And to retrieve the passed in values:
#{
string valuePassedIn = this.ViewData.ContainsKey("VariableName") ?
this.ViewData["VariableName"].ToString() : string.Empty;
}
Hope this helps...

Implement data from the database to input field

I am trying to send data from database when pressing Edit on ActionLink(Last line on view). Having some problem in view Controller to understand how this data will be sent to the right input field so i can Edit it and send it back to database.
(This Question is only for the GET part so i have not included Post controller)
View(view name AddCourse):
#model IEnumerable<CadProject.Models.CourseModel>
#using (Html.BeginForm("AddCourse", "AdminModel", null, FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
<div class="form-group">
#Html.Label("Emnekode", new { #class = "control-label col-md-2" })
<div class="col-md-10">
<input name="CourseCode" type="text" />
</div>
</div>
<div class="form-group">
#Html.Label("Emne", new { #class = "control-label col-md-2" })
<div class="col-md-10">
<input name="CourseName" type="text" />
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Upload" class="btn btn-default" />
</div>
</div>
</div>
}
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.CourseCode)
</th>
<th>
#Html.DisplayNameFor(model => model.CourseName)
</th>
<th>
Edit/Delete
</th>
</tr>
#if (Model != null)
{
foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.CourseCode)
</td>
<td>
#Html.DisplayFor(modelItem => item.CourseName)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = item.Id }) |
#Html.ActionLink("Delete", "Delete", new { id = item.Id })
</td>
</tr>
}
}
</table>
View controller:
public ActionResult Edit(int id)
{
db.Courses.Find(id);
CourseModel model = db.Courses.Find(id);
if(model == null)
{
return View();
}
return View("AddCourse", db.Courses);
}
Database model:
public class CourseModel
{
[Key]
public int Id { get; set; }
public string CourseCode { get; set; }
public string CourseName { get; set; }
}
The functionality the Edit(HttpGet) method is to take the ID of the item the user want's to edit, find ONE record in the database with this ID and send the found record to the Edit view where you have various controls that display the current values and allow the user to change them through the use of text boxes e.t.c
In the code you posted you look up the course the user wants to edit but then you return all the courses to the Edit view, which is wrong.It's supposed to be like this:
public ActionResult Edit(int id)
{
db.Courses.Find(id);
CourseModel model = db.Courses.Find(id);
if(model == null)
{
return View();
}
return View("EditCourse", model);
}
Have a look at this simple example:
Edit Action:
[HttpGet]
public ActionResult Edit(int id)
{
var course1 = new Course { ID = 0, Name = "Software Engineering" };
var course2 = new Course { ID = 1, Name = "ASP.NET MVC" };
var course3 = new Course { ID = 2, Name = "C# Course" };
var courses = new List<Course> { course1 ,course2, course3};
var course = courses.FirstOrDefault(c => c.ID == id);
return View("EditCourse",course);
}
EditCourse View:
#model Example.Models.Course
#{
ViewBag.Title = "EditCourse";
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Course</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.ID)
<div class="form-group">
#Html.LabelFor(model => model.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Name, "", new { #class = "text-danger" })
</div>
</div>
<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>
}

MVC ViewModel collections are null on postback

I have a view model which contains a class reference and 2 IEnumerable collections.
public class BuildingTypeViewModel
{
public Static_Item BuildingType { get; set; }
public IEnumerable<Reading_Type> ReadingTypes { get; set; }
public IEnumerable<Building_Type_Reading> BuildingReadings { get; set; }
}
I populate this ViewModel in the Edit action on the controller
public ActionResult Edit(int id)
{
Static_Item staticItem = db.Static_Item.Find(id);
BuildingTypeViewModel model = new BuildingTypeViewModel
{
BuildingType = staticItem,
ReadingTypes=db.Reading_Type.ToList(),
BuildingReadings = db.Building_Type_Reading.Where(bt => bt.UN_Building_Type == staticItem.UN_Building_Type).ToList()
};
return View(model);
}
Building type on the view model is a class with an ID and Description and the data would be something like this:
UN_Building_Type=1, Description = "Hospital"
The IEnumerable of Reading_Type's would be like this:
UN_Reading_Type = 1, Description = "Electric"
UN_Reading_Type = 2, Description = "Gas"
The IEnumerable of Building_Type_Readings would be like this:
UN_Building_Type_Readings=1, UN_Building_Type=1, UN_Reading_Type = 1, Typical=300, Good=150
UN_Building_Type_Readings=2, UN_Building_Type=1, UN_Reading_Type = 2, Typical=800, Good=400
I load this data into my view:
#model SSE.Enterprise.EE_Web_Portal.Models.BuildingTypeViewModel
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Static_Item</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.BuildingType.UN_Building_Type)
<div class="form-group">
#Html.LabelFor(model => model.BuildingType.Description, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.BuildingType.Description, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.BuildingType.Description, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2">Building Readings</label>
<div class="col-md-10">
<table class="table">
<tr>
<th/>
<th>
#Html.LabelFor(model=>model.BuildingReadings.FirstOrDefault().Typical)
</th>
<th>
#Html.LabelFor(model => model.BuildingReadings.FirstOrDefault().Good)
</th>
</tr>
#foreach (var item in Model.ReadingTypes)
{
<tr>
<td>
#Html.Label(item.Description)
</td>
<td>
#Html.EditorFor(model => model.BuildingReadings.FirstOrDefault(b => b.UN_Reading_Type == item.UN_Reading_Type).Typical, new { htmlAttributes = new { #class = "form-control" } })
</td>
<td>
#Html.EditorFor(model => model.BuildingReadings.FirstOrDefault(b => b.UN_Reading_Type == item.UN_Reading_Type).Good, new { htmlAttributes = new { #class = "form-control" } })
</td>
</tr>
}
</table>
</div>
</div>
<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>
Here is my edit postback method.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "BuildingType")] BuildingTypeViewModel model)
{
if (ModelState.IsValid)
{
db.Entry(model).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
When the page is posted back the 2 IEnumerable collections are null so I'm not able to save the data that has been entered.
Any idea?
Ta
First of all, by specifying the attribute Bind(Include = "BuildingType") you're telling MVC to only bind this single property. Remove the attribute, and MVC will try to bind your 2 IEnumerable collections.
Next, check your #Html.EditorFor calls. I'm not sure MVC can understand FirstOrDefault inside. Try avoiding LINQ selectors inside your view.
And after that, as #will mentioned, try changing IEnumerable to List.

Resources