I am beginner in .NET and I am learning MVC pattern. I am building a demo application which demonstrate CRUD Operations on any product. Before I use any database connectivity, I decided to test it with in memory variable. My code is as below:
Model Class
public class Product
{
[Key]
public int Id { get; set; }
[Required]
[Display(Name="Name")]
public String ProductName { get; set; }
[Required]
[Display(Name="Price")]
public float Price { get; set; }
[Display(Name="Discription")]
public String ProductDiscription { get; set; }
}
Controller Class
public class ProductController : Controller
{
//
// GET: /Product/
public ActionResult Index()
{
return View(new Product{Id = 01, Price=100, ProductName="MyProduct", ProductDiscription="30 TeaBags in a pack"});
}
}
View
#model CrudAppDemo.Models.Product
#{
ViewBag.Title = "Products";
}
<h2>Products</h2>
<div class="row">
<div class="col-md-4 ">
<table class="table">
<thead>
<tr>
<td>#Html.DisplayNameFor(model => model.Name)</td>
<td>#Html.DisplayNameFor(model => model.Price)</td>
<td>#Html.DisplayNameFor(model => model.Description)</td>
</tr>
</thead>
<tbody>
<tr>
<td>#Html.DisplayFor(model => model.Name)</td>
<td>#Html.DisplayFor(model => model.Price)</td>
<td>#Html.DisplayFor(model => model.Description)</td>
</tr>
</tbody>
</table>
</div>
</div>
When I am running this code, I am getting CS0411 Error:
Your parameter doesn't match the ones in model.
you have ProductName in model and you are trying to access Name which is not in model, same goes for description.
Write this instead.
<thead>
<tr>
<td>#Html.DisplayNameFor(model => model.ProductName)</td>
<td>#Html.DisplayNameFor(model => model.Price)</td>
<td>#Html.DisplayNameFor(model => model. ProductDiscription)</td>
</tr>
</thead>
<tbody>
<tr>
<td>#Html.DisplayFor(model => model.ProductName)</td>
<td>#Html.DisplayFor(model => model.Price)</td>
<td>#Html.DisplayFor(model => model. ProductDiscription)</td>
</tr>
</tbody>
It looks like you are using the display name [Display(Name="Name")] as your property rather than the property itself public String ProductName { get; set; }. Try changing to use the property name.
<td>#Html.DisplayNameFor(model => model.ProductName)</td>
<td>#Html.DisplayNameFor(model => model.Price)</td>
<td>#Html.DisplayNameFor(model => model.ProductDiscription)</td>
Related
When passing ViewModel to View I get the error
The model item passed into the ViewDataDictionary is of type
'System.Collections.Generic.List'1[TraficAlert.Models.TaBarHeader]',
but this ViewDataDictionary instance requires a model item of type
'System.Collections.Generic.IEnumerable'1[TraficAlert.Models.ViewModels.HeaderTelegramaViewModel]'.
I have tried to use #model IEnumerable<TraficAlert.Models.ViewModels.HeaderTelegramaViewModel> in the Index.cshtml and it works, but I need to access a property from HeaderTelegramaViewModel.
Index.cshtml:
#model IEnumerable<TraficAlert.Models.ViewModels.HeaderTelegramaViewModel>
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.TaBarHeader.Id)
</th>
<th>
#Html.DisplayNameFor(model => model.TaBarHeader.ParentId)
</th>
<th>
#Html.DisplayNameFor(model => model.TaBarHeader.TStamp)
</th>
(...)
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.TaBarHeader.Id)
</td>
<td>
#Html.DisplayFor(modelItem => item.TaBarHeader.ParentId)
</td>
<td>
#Html.DisplayFor(modelItem => item.TaBarHeader.TStamp)
</td>
(...)
<td>
<a asp-action="Edit" asp-route-id="#item.TaBarHeader.Id">Edit</a> |
<a asp-action="Details" asp-route-id="#item.TaBarHeader.Id">Details</a> |
<a asp-action="Delete" asp-route-id="#item.TaBarHeader.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
HeaderTelegramaController:
(...)
public IActionResult Index()
{
var applicationDbContext = _unitofwork.BarHeader.GetAllBarH().ToList();
return View(applicationDbContext);
}
TaBarHeaderRepository:
public IEnumerable<TaBarHeader> GetAllBarH()
{
return _db.TaBarHeaders
.Include(t => t.CategoriaFk)
.Include(t => t.CauzaFk)
.Include(t => t.ClasaFk)
.Include(t => t.LucrareFk)
.Include(t => t.RegionalaFk)
.Include(t => t.UserFk);
}
HeaderTelegramaViewModel:
public TaBarHeader TaBarHeader { get; set; }
public IEnumerable<SelectListItem> Categoria { get; set; }
public IEnumerable<ViewOtf> ViewOTFCollection { get; set; }
(...)
Why do I get the above mentioned error?
Thank you.
use the model below in the cshtml.
#model TraficAlert.Models.ViewModels.HeaderTelegramaViewModel
And in the Index() create an instance of HeaderTelegramaViewModel:
var _HeaderTelegramaViewModel = new HeaderTelegramaViewModel();
_HeaderTelegramaViewModel.TaBarHeader = TaBarHeader;
And the class HeaderTelegramaViewModel must have:
public IEnumerable<TaBarHeader> TaBarHeader { get; set; }
public IEnumerable<SelectListItem> Categoria { get; set; }
public IEnumerable<ViewOtf> ViewOTFCollection { get; set; }
use :: #model TraficAlert.Models.ViewModels.HeaderTelegramaViewModel
instead of ::#model IEnumerable<TraficAlert.Models.ViewModels.HeaderTelegramaViewModel>
at the top of index.cshtml page
See the type of your model IEnumerable<TraficAlert.Models.ViewModels.HeaderTelegramaViewModel> and apply this:
public IActionResult Index()
{
var applicationDbContext = _unitofwork.BarHeader.GetAllBarH();
return View(applicationDbContext);
}
The error message explains the problem fairly clearly: you're passing in a different type than the view is expecting.
Specifically, you call GetAllBarH() to get the data for the view, and it returns IEnumerable<TaBarHeader>. Therefore the model declaration for the page should be:
#model IEnumerable<TraficAlert.Models.TaBarHeader>
If you really wanted HeaderTelegramaViewModel then you're going to have to convert the IEnumerable<TaBarHeader> somehow. I assume you missed that step in your controller.
Why do I get the above mentioned error?
Because the data type returned in your action is not the same as the data type required on the view.
You can modify your HeaderTelegramaController like this:
public IActionResult Index()
{
var applicationDbContext = _unitofwork.BarHeader.GetAllBarH().Select(m => new HeaderTelegramaViewModel { TaBarHeader = m }).ToList();
return View(applicationDbContext);
}
A possible error I assume may come from your table head
try specifying an index considering your model is an IEnumerable.
So change
#Html.DisplayFor(modelItem => item.TaBarHeader.Id)
to something like this
#Html.DisplayFor(modelItem => item[0].TaBarHeader.Id)
I come from a procedural PHP background. I am trying to learn how to do the same stuff I used to do in php and do it in asp.net. I need to sort my orders under their perspective Customer name.
Right now my layout is:
Sams Club
Order 1
Sams Club
Order 2
Walmart
Order 1
Walmart
Order 2
Walmart
Order 3
I need my layout more like:
Sams Club
Order 1
Order 2
Walmart
Order 1
Order 2
Order 3
I can easily do this with PHP but am struggling to figure out how to do this in an MVC type of application.
This is how my cshtml page looks.
<table class="table table-condensed" style="border-collapse:collapse;">
<thead>
<tr>
<th><input type="checkbox" name="CheckOrders" />Select All </th>
<th>Bill to Name</th>
<th>Qty Ordered</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr data-toggle="collapse" data-target="##Html.DisplayFor(model => item.customer_number)" class="accordion-toggle">
<td><input type="checkbox" name="checkAllCus" value="#Html.DisplayFor(model => item.customer_number)"/></td>
<td>
#Html.DisplayFor(model => item.Bill_to_Name)
</td>
<td>
#Html.DisplayFor(model => item.Quantity_Ordered)
</td>
</tr>
<tr>
<td colspan="12" class="hiddenRow">
<div class="accordian-body collapse" id="#Html.DisplayFor(model => item.customer_number)">
<table class="table table-striped">
<thead>
<tr>
<th></th>
<th>Order Number</th>
<th>Bill to Name</th>
<th>Qty Ordered</th>
<th>Ship Date</th>
<th>Item #</th>
<th>Description</th>
<th>State</th>
<th>Carrier</th>
<th>Details</th>
</tr>
</thead>
<tbody>
<tr>
<td><input type="checkbox" name="orders[]" value="#Html.DisplayFor(model => item.order_no)" /></td>
<td>
#Html.DisplayFor(model => item.order_no)
</td>
<td>
#Html.DisplayFor(model => item.Bill_to_Name)
</td>
<td>
#Html.DisplayFor(model => item.Quantity_Ordered)
</td>
<td>
#Html.DisplayFor(model => item.Ship_Date)
</td>
<td>
#Html.DisplayFor(model => item.item_no)
</td>
<td>
#Html.DisplayFor(model => item.descr1)
</td>
<td>
#Html.DisplayFor(model => item.shipstate)
</td>
<td>
#Html.DisplayFor(model => item.shipper)
</td>
<td>
#Html.ActionLink("Details", "Details", new { /* id=item.PrimaryKey */ })
</td>
</tr>
</tbody>
</table>
</div>
</td>
</tr>
}
</tbody>
</table>
Below is my Model
namespace OpenCustomerOrder3.Models
{
using System;
public partial class spOrderDetails_Result
{
public string cusNo;
public string customer_number { get; set; }
public string shipper { get; set; }
public string user_def_fld_5 { get; set; }
public string item_no { get; set; }
public string descr1 { get; set; }
public string location1 { get; set; }
public string Bill_to_Name { get; set; }
public Nullable<decimal> Quantity_Ordered { get; set; }
public string Requested_Date { get; set; }
public string Ship_Date { get; set; }
public string Status { get; set; }
public string order_no { get; set; }
public string shipstate { get; set; }
}
}
This really comes down to what your domain model looks like.
It looks like the model type being passed to the view at the moment is simply an IEnumerable<Order> or similar. If you really have to do this way, one way to achieve what you want is by grouping your orders by CustomerName using the GroupBy method in System.Linq.
However, a better model would be to let a Customer model have many orders, i.e. between Customer and Order there is a 1..* relationship. For example,
public class Customer
{
public string Name { get; set; }
public IEnumerable<Order> Orders { get; set; }
}
Then what you want is to pass an IEnumerable<Customer> instead to your view.
I have a view that displays a boolean (currently defaulted to 0) in a box format in the view that I cannot check to activate as true and also want to enter text in the result field to pass back to the controller and save both changes to a table. Can someone please explain what I have to do to allow this functionality to work please.
Controller code
public ActionResult P1A1Mark()
{
List<MarkModel> query = (from row in db.submits
where row.assignment_no.Equals("1") && row.group_no == 1
group row by new { row.assignment_no, row.student_no, row.student.firstname, row.student.surname } into g
select new MarkModel
{
student_no = g.Key.student_no,
student_surname = g.Key.surname,
student_firstname = g.Key.firstname
}
).ToList();
return View(query);
}
View
#model IEnumerable<MvcApplication2.Models.MarkModel>
#{
ViewBag.Title = "P1A1Mark";
}
<h2>Mark Student Assignments</h2>
<table>
<tr>
<th>
#Html.DisplayNameFor(model => model.student_no)
</th>
<th>
#Html.DisplayNameFor(model => model.student_surname)
</th>
<th>
#Html.DisplayNameFor(model => model.student_firstname)
</th>
<th>
#Html.DisplayNameFor(model => model.submitted)
</th>
<th>
#Html.DisplayNameFor(model => model.result)
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.student_no)
</td>
<td>
#Html.DisplayFor(modelItem => item.student_surname)
</td>
<td>
#Html.DisplayFor(modelItem => item.student_firstname)
</td>
<td>
#Html.DisplayFor(modelItem => item.submitted)
</td>
<td>
#Html.DisplayFor(modelItem => item.result)
</td>
</tr>
}
</table>
Model
public class MarkModel
{
public string student_no { get; set; }
public string student_surname { get; set; }
public string student_firstname { get; set; }
public string assignment_no { get; set; }
public bool submitted { get; set; }
public string result { get; set; }
public Nullable<int> group_no { get; set; }
}
Create an EditorTemplate for type of MarkModel.
In /Views/Shared/EditorTemplates/MarkModel.cshtml
#model MvcApplication2.Models.MarkModel
<tr>
<td>#Html.DisplayFor(m => m.student_no)</td>
<td>#Html.DisplayFor(m => m.student_surname)</td>
<td>#Html.DisplayFor(m => m.student_firstname)</td>
<td>#Html.CheckBoxFor(m => m.submitted)</td>
<td>#Html.TextBoxFor(m => m.result)</td>
</tr>
and in the main view
#model IEnumerable<MvcApplication2.Models.MarkModel>
#using (Html.BeginForm())
{
<table>
<thead>
// add your th elements
</thead>
<tbody>
#Html.EditorFor(m => m)
<tbody>
</table>
<input type="submit" ../>
}
and create a method to post back to
[HttpPost]
public ActionResult P1A1Mark(IEnumerable<MarkModel>model)
Alternatively you can use a for loop in the view (the model must be IList<T>)
for(int i = 0; i < Model.Count; i++)
{
....
#Html.CheckBoxFor(m => m[i].submitted)
}
I have a problem with the action that recives a complex ViewModel by POST and all of its object components are null, even though I have initialized them in Action and have returned the whole ViewModel to the View using GET method.
Let me explain the situation. I have a complex model for a View that consists of three sections: Applicant details, Application details, and a list of Recordings. This View is complex to (1) let me see the details of Applicant I am creating application for, (2) have a list of recordings I would like to choose from which then I can add to Application. This is my ViewModel:
public class ApplicantApplicationRecordingsViewModel
{
// Applicant
public Applicant Applicant { get; set; }
// Application
public Application Application { get; set; }
public SelectList UsageTypeSelectList { get; private set; }
public SelectList UsageEndAppSelectList { get; private set; }
// Recordings
public IEnumerable<RecordingViewModelApp>
RecordingsViewModelApp { get; set; }
public ApplicantApplicationRecordingsViewModel()
: this(new MyDBContext())
{
}
public ApplicantApplicationRecordingsViewModel(MyDBContext dbContext)
{
PopulateUsageTypeSelectList(dbContext);
PupulateUsageEndAppSelectList(dbContext);
}
private void PopulateUsageTypeSelectList(MyDBContext dbContext,
int? usageTypeSelected = null)
{
IEnumerable<UsageType> utQuery =
dbContext.UsageTypes.OrderBy(
ut => ut.UsageTypeName).ToList();
this.UsageTypeSelectList =
new SelectList(utQuery,
"UsageTypeID",
"UsageTypeName",
usageTypeSelected);
}
private void PupulateUsageEndAppSelectList(
MyDBContext dbContext,
int? usageEndAppSelected = null)
{
IEnumerable<UsageEndApp> ueaQuery =
dbContext.UsageEndApps.OrderBy(uea => uea.UsageEndAppName).ToList();
this.UsageEndAppSelectList =
new SelectList(ueaQuery,
"UsageEndAppID",
"UsageEndAppName",
usageEndAppSelected);
}
}
In the controller I simply populate a list of recordings for RecordingViewModelApp, put details of an applicant to Applicant and leave the Application object empty to be filled in a View.
public ActionResult Create(int? ApplicantID)
{
if (ApplicantID == null)
{
// Error 400. Bad Request Exception
}
ApplicantApplicationRecordingsViewModel viewModel = null;
using (MyDBContext dbContext = new MyDBContext())
{
Applicant applicant =
dbContext.Applicants.Find(ApplicantID);
if (applicant == null)
{
// Error 404. Http not found
}
List<RecordingViewModelApp> recordings =
getViewModel(
dbContext.Recordings.ToList(),
dbContext);
viewModel =
new ApplicantApplicationRecordingsViewModel(dbContext);
viewModel.Applicant = applicant;
viewModel.RecordingsViewModelApp = recordings;
}
return View(viewModel);
}
The problem is that when I return the ViewModel (ApplicantApplicationRecordingsViewModel) back to the [HttpPost] Create() Action, all the View Model's components are null, e.g. the list of RecordingViewModelApp is null. What Am I missing? I would need to understand what's going on behind the scene and why default model binding doesn't work.
[HttpPost]
[ActionName("Create")]
public ActionResult Create_post(
ApplicantApplicationRecordingsViewModel viewModelToValidate)
{
// Validation against Application only and TryToUpdate() etc.
}
CHeers!
EDIT:
The View
#model Project.ApplicantApplicationRecordingsViewModel
#{
string applicantDetails = string.Format("{0} {1} {2}",
Model.Applicant.title, Model.Applicant.firstName, Model.Applicant.lastName);
ViewBag.Title = "Create a new application for " + applicantDetails;
}
<h2>#ViewBag.Title</h2>
<hr />
#using (Html.BeginForm())
{
<h3>Details of the applicant</h3>
#Html.HiddenFor(item => Model.Applicant.ApplicantID)
#Html.HiddenFor(item => Model.Application.ApplicationID)
<table>
<tr>
<th>#Html.DisplayNameFor(item => Model.Applicant.title)</th>
<th>#Html.DisplayNameFor(item => Model.Applicant.firstName)</th>
<th>#Html.DisplayNameFor(item => Model.Applicant.lastName)</th>
<th>#Html.DisplayNameFor(item => Model.Applicant.telephone)</th>
<th>#Html.DisplayNameFor(item => Model.Applicant.mobile)</th>
<th>#Html.DisplayNameFor(item => Model.Applicant.email)</th>
</tr>
<tr>
<td class="display-field">#Html.DisplayFor(item => Model.Applicant.title)</td>
<td class="display-field">#Html.DisplayFor(item => Model.Applicant.firstName)</td>
<td class="display-field">#Html.DisplayFor(item => Model.Applicant.lastName)</td>
<td class="display-field">#Html.DisplayFor(item => Model.Applicant.telephone)</td>
<td class="display-field">#Html.DisplayFor(item => Model.Applicant.mobile)</td>
<td class="display-field">#Html.DisplayFor(item => Model.Applicant.email)</td>
</tr>
</table>
<hr /> // ----------------------------------------------------------------------------------------------
<h3>Details of the application</h3>
<table id="main">
<tr>
<td>
<table>
<tr>
<td class="editor-label first-label">#Html.DisplayNameFor(item => Model.Application.ApplicationNo)</td>
<td class="editor-field">
#Html.EditorFor(item => Model.Application.ApplicationNo)
#Html.ValidationMessageFor(item => Model.Application.ApplicationNo)
</td>
</tr>
<tr>
<td class="editor-label first-label">#Html.DisplayNameFor(item => Model.Application.StartDate)</td>
<td class="editor-field">
#Html.EditorFor(item => Model.Application.StartDate)
#Html.ValidationMessageFor(item => Model.Application.StartDate)
</td>
</tr>
<tr>
<td class="editor-label first-label">#Html.DisplayNameFor(item => Model.Application.EndDate)</td>
<td class="editor-field">
#Html.EditorFor(item => Model.Application.EndDate)
#Html.ValidationMessageFor(item => Model.Application.EndDate)
</td>
</tr>
<tr>
<td class="editor-label first-label">#Html.DisplayNameFor(item => Model.Application.UsageTypeID)</td>
<td class="editor-field">
#Html.DropDownListFor(item => Model.Application.UsageTypeID, Model.UsageTypeSelectList, "-- Select Usage --")
#Html.ValidationMessageFor(item => Model.Application.UsageTypeID)
</td>
</tr>
<tr>
<td class="editor-label first-label">#Html.DisplayNameFor(item => Model.Application.UsageEndAppID)</td>
<td class="editor-field">
#Html.DropDownListFor(item => Model.Application.UsageEndAppID, Model.UsageEndAppSelectList, "-- Select Type --")
#Html.ValidationMessageFor(item => Model.Application.UsageEndAppID)
</td>
</tr>
<tr>
<td class="editor-label first-label">#Html.DisplayNameFor(item => Model.Application.linkToPaperVer)</td>
<td class="editor-field">
#Html.EditorFor(item => Model.Application.linkToPaperVer)
#Html.ValidationMessageFor(item => Model.Application.linkToPaperVer)
</td>
</tr>
</table>
</td>
<td class="editor-label">
#Html.DisplayNameFor(item => Model.Application.Info)
</td>
<td class="editor-field">
#Html.EditorFor(item => Model.Application.Info)
#Html.ValidationMessageFor(item => Model.Application.Info)
</td>
</tr>
</table>
<hr /> // ----------------------------------------------------------------------------------------------
<h3>List of recordings</h3>
Html.RenderPartial("~/Views/Recordings/_List_App.cshtml", Model.RecordingsViewModelApp);
<hr /> // ----------------------------------------------------------------------------------------------
<p>
<input type="submit" value="Create" />
</p>
}
<div>
#Html.ActionLink("Back to List", "Index", "Applicants")
</div>
EDIT 2
PartialView:
#model IEnumerable<Project.ViewModels.RecordingViewModelApp>
#if (Model != null)
{
<div>
<table class="data-in-table">
<tr>
<th>#Html.DisplayNameFor(model => model.IsSelected)</th>
<th>#Html.DisplayNameFor(model => model.FileLocation)</th>
<th>#Html.DisplayNameFor(model => model.EnteredDate)</th>
<th>#Html.DisplayNameFor(model => model.Duration)</th>
<th>#Html.DisplayNameFor(model => model.Status)</th>
</tr>
#foreach (var item in Model)
{
<tr>
<td class="display-field">#Html.EditorFor(model => item.IsSelected)</td>
<td class="display-field">#Html.DisplayFor(model => item.FileLocation)</td>
<td class="display-field">#Html.DisplayFor(model => item.EnteredDate)</td>
<td class="display-field">#Html.DisplayFor(model => item.Duration)</td>
<td class="display-field">#Html.DisplayFor(model => item.Status)</td>
</tr>
}
</table>
</div>
}
else
{
<h3>No recordings attached to this Patient</h3>
}
EDIT 3
The RecordingViewModelApp:
public class RecordingViewModel
{
public int RecordingID { get; set; }
public string FileLocation { get; set; }
public DateTime EnteredDate { get; set; }
public int Duration { get; set; }
public string Status { get; set; }
}
public class RecordingViewModelApp : RecordingViewModel
{
public bool IsSelected { get; set; }
}
First to fix the view model. A view model should only contain simple properties representing what you want to display and/or edit
View model
public class ApplicantApplicationRecordingsViewModel
{
public Applicant Applicant { get; set; }
public Application Application { get; set; }
public IEnumerable<RecordingViewModelApp> Recordings { get; set; }
public string Title { get; set; }
public SelectList UsageTypeSelectList { get; private set; }
public SelectList UsageEndAppSelectList { get; private set; }
}
Controller (note validation checks omitted)
public ActionResult Create(int ApplicantID) // assume you must have a custom route for this?
{
ApplicantApplicationRecordingsViewModel viewModel = new ApplicantApplicationRecordingsViewModel();
Applicant applicant = dbContext.Applicants.Find(ApplicantID);
viewModel.Applicant = applicant;
viewModel.Title = string.Format("Create a new application for {0} {1} {2}", applicant.title, applicant.firstName, applicant.lastName);
viewModel.Recordings = getViewModel(dbContext.Recordings.ToList(), dbContext); // not sure what this is?
viewModel.UsageTypeSelectList = new SelectList(dbContext.UsageTypes.OrderBy(ut => ut.UsageTypeName), "UsageTypeID", "UsageTypeName");
viewModel.UsageEndAppSelectList = new SelectList(dbContext.UsageEndApps.OrderBy(uea => uea.UsageEndAppName), "UsageEndAppID", "UsageEndAppName");
return View(viewModel);
}
View
#model Project.ApplicantApplicationRecordingsViewModel
<h2>#Model.Title</h2>
#using (Html.BeginForm())
{
#Html.HiddenFor(item => Model.Applicant.ApplicantID) // include for post back but Application.ApplicationID not necessary (its a new application!)
<h3>Details of the applicant</h3>
// Add display detail for applicant, but use css for layout (position, floats etc), not tables (which are for tabular data)
<h3>Details of the application</h3>
// Add controls for Application but use LabelFor() so the label is associated with the control (otherwise its not a label)
#Html.DisplayNameFor(m => m.Application.ApplicationNo)
#Html.EditorFor(m => m.Application.ApplicationNo)
#Html.ValidationMessageFor(m => m.Application.ApplicationNo)
....
<h3>List of recordings</h3>
<table>
<thead>
.... // add table headings
</thead>
<tbody>
#Html.EditorFor(m => m.Recordings) // This uses a custom editor template to display and select recordings
</tbody>
</table>
<input type="submit" value="Create" />
}
EditorTemplate (/Views/Shared/EditorTemplates/RecordingViewModelApp.cshtml)
Note you must use either a for loop or a custom EditorTemplate to render collections. The foreach loop you used just renders duplicate id (invalid html) and name attributes without the correct indexers so will not post back to a collection.
#model RecordingViewModelApp
<tr>
<td class="display-field">
#Html.CheckBoxFor(m => m.IsSelected) // required for postback
#Html.HiddenFor(m => m.RecordingID) // required for postback
</td>
<td class="display-field">#Html.DisplayFor(m => m.FileLocation)</td>
.... // other display properties
</tr>
POST method
[HttpPost]
public ActionResult Create(ApplicantApplicationRecordingsViewModel model)
{
// model is now bound with the Applicant ID, all the properties of Application
// and the collection of Recordings with their ID and IsSelected property.
}
My Model:
public class SendFileDeviceViewModel
{
public SendFileDeviceViewModel()
{
PolicyList = new List<SendFileDevicePoliciesViewModel>();
}
public string DeviceName { get; set; }
public int DeviceId { get; set; }
public string ManagementGroupName { get; set; }
public int ManagementGroupId { get; set; }
public bool ReloadConfiguration { get; set; }
public bool ImmediateSend { get; set; }
public DateTime TimeToSend { get; set; }
public List<SendFileDevicePoliciesViewModel> PolicyList { get; set; }
}
public class SendFileDevicePoliciesViewModel
{
public int PackageTemplateId { get; set; }
public string PolicyName { get; set; }
public string PolicyType { get; set; }
public string DefinedAt { get; set; }
public bool ResendPolicy { get; set; }
}
My View:
<h2>Send files to a Device #Model.DeviceName</h2>
<h3>Reload configuration settings</h3>
#Html.CheckBoxFor(m => m.ReloadConfiguration) #Html.LabelFor(m => m.ReloadConfiguration)
<h3>Select the policies to reload</h3>
#using (Html.BeginForm())
{
#Html.HiddenFor(m => m.DeviceId)
#Html.HiddenFor(m => m.ManagementGroupId)
#Html.ValidationSummary(true)
if (Model.PolicyList.Count() > 0)
{
<table>
<caption>
Policies available for this device</caption>
<thead>
<tr>
<th scope="col">
</th>
<th scope="col">
Policy Name
</th>
<th scope="col">
Policy Type
</th>
<th scope="col">
Defined At
</th>
</tr>
</thead>
<tbody>
#foreach (var policies in Model.PolicyList)
{
<tr>
#*<td>#Html.CheckBox("PackageTemplateId", new { value = policies.PackageTemplateId })</td>*#
<td>#Html.CheckBoxFor(m => policies.ResendPolicy)</td>
<td>#policies.PolicyName</td>
<td>#policies.PolicyType</td>
<td>#policies.DefinedAt</td>
</tr>
}
</tbody>
</table>
}
<div class="editor-label">
#Html.LabelFor(m => m.ImmediateSend)
</div>
<div class="editor-field">
#Html.CheckBoxFor(m => m.ImmediateSend)
</div>
<div class="editor-label">
#Html.LabelFor(m => m.TimeToSend)
</div>
<div class="editor-field">
#Html.EditorFor(m => m.TimeToSend)
</div>
<p>
<input type="submit" value="Send files" /></p>
My issue is when retrieving model from controller the PolicyList is always empty. Am I missing somethign simple here?
Two problems:
Your first problem is that you are resetting your list in your constructor, so when the form is posted and the model binder instantiates an instance of your model, you're re-setting the list. Change it to do a coalesce to only re-assign if the list is null:
public SendFileDeviceViewModel()
{
PolicyList = PolicyList ?? new List<SendFileDevicePoliciesViewModel>();
}
Your next problem is your foreach. In order to index the name attribute correctly (so the model binder can do it's stuff), you need to use a for loop. Also, keep the Id in a HiddenFor.
Try this in place of your foreach:
#for (int i = 0; i < Model.PolicyList.Count; i++)
{
<tr>
<td>
#Html.HiddenFor(m => m.PolicyList[i].PackageTemplateId)
#Html.CheckBoxFor(m => m.PolicyList[i].ResendPolicy)
</td>
<td>#Model.PolicyList[i].PolicyName</td>
<td>#Model.PolicyList[i].PolicyType</td>
<td>#Model.PolicyList[i].DefinedAt</td>
</tr>
}
The reason for this is because you didn't respect the naming convention of your input fields. You should replace the foreach loop in the view with a for loop or a custom editor template:
<tbody>
#for (var i = 0; i < Model.PolicyList.Count; i++)
{
<tr>
<td>#Html.CheckBoxFor(x => x.PolicyList[i].ResendPolicy)</td>
<td>#Html.DisplayFor(x => x.PolicyList[i].PolicyName)</td>
<td>#Html.DisplayFor(x => x.PolicyList[i].PolicyType)</td>
<td>#Html.DisplayFor(x => x.PolicyList[i].DefinedAt)</td>
</tr>
}
</tbody>
Also now only the ResendPolicy property will be bound because that's the only one that has a corresponding input field (a checkbox in your case). If you want to bind the others as well you might need to include corresponding hidden fields:
<tbody>
#for (var i = 0; i < Model.PolicyList.Count; i++)
{
<tr>
<td>
#Html.HiddenFor(x => x.PolicyList[i].PackageTemplateId)
#Html.CheckBoxFor(x => x.PolicyList[i].ResendPolicy)
</td>
<td>
#Html.HiddenFor(x => x.PolicyList[i].PolicyName)
#Html.DisplayFor(x => x.PolicyList[i].PolicyName)
</td>
<td>
#Html.HiddenFor(x => x.PolicyList[i].PolicyType)
#Html.DisplayFor(x => x.PolicyList[i].PolicyType)
</td>
<td>
#Html.HiddenFor(x => x.PolicyList[i].DefinedAt)
#Html.DisplayFor(x => x.PolicyList[i].DefinedAt)
</td>
</tr>
}
</tbody>