Why the model is null in Action method in ASP.NET MVC? - asp.net-mvc

my problem is that after click on button "Save", I get the model passed to controller from view is null.
Here my code of the View and Controller.
Do you know where I am doing wrong?
Thank you so much.
View Certificazioni.cshtml
#model List<ElencoCertificazioniItem>
...
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
...
<tbody>
#{
for (int i = 0; i < Model.Count; i++)
{
<tr>
<td>#Html.LabelFor(m => Model[i].Id) </td>
<td>#Html.LabelFor(m => Model[i].description)</td>
<td>#Html.EditorFor(m => Model[i].Field1.Value)</td>
<td>#Html.EditorFor(m => Model[i].Field2.Value)</td>
</tr>
}
}
</tbody>
}
....
}
Controller
[HttpPost]
[ValidateAntiForgeryToken]
[HttpParamAction]
public ActionResult SaveItems(List<ElencoCertificazioniItem> model)
{
//the items here is null!!! ;(
return saveItems(model);
}
Model
public class ElencoCertificazioniItem
{
public int Id { get; set; }
public string description { get; set; }
public bool? Field1 { get; set; }
public bool? Field2 { get; set; }
}
I use HttpParamAction to manage calls to different methods controller (I have 2 button in the same form).
public class HttpParamActionAttribute : ActionNameSelectorAttribute
{
public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)
{
if (actionName.Equals(methodInfo.Name, StringComparison.InvariantCultureIgnoreCase))
return true;
var request = controllerContext.RequestContext.HttpContext.Request;
return request[methodInfo.Name] != null;
}
}
The list is not null and contains right count elements, but the items within are null and Id properties is always 0!

Your ID and description will always be 0 and null because you don't use any input to post them in form. If you want return them, you have to use input hiddent to pass this values
<tr>
<td>
#Html.LabelFor(m => Model[i].Id)
#Html.HiddenFor(m => Model[i].Id)
</td>
<td>
#Html.LabelFor(m => Model[i].description)
#Html.HiddenFor(m => Model[i].description)
</td>
<td>#Html.EditorFor(m => Model[i].Field1)</td>
<td>#Html.EditorFor(m => Model[i].Field2)</td>
</tr>

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 using IEnumerable<Model> with Razor is null after post to controller

My Model
public class ActivityModel
{
public int Id { get; set; }
public String Nick { get; set; }
public String TripName { get; set; }
[DisplayFormat(DataFormatString = "{0:hh\\:mm}", ApplyFormatInEditMode = true)]
public TimeSpan? FromTime { get; set; }
[DisplayFormat(DataFormatString = "{0:hh\\:mm}", ApplyFormatInEditMode = true)]
public TimeSpan? ToTime { get; set; }
public String FromPlace { get; set; }
public String ToPlace { get; set; }
public String activityType { get; set; }
[DisplayFormat(DataFormatString = "{0:dd.MM.yyyy}", ApplyFormatInEditMode = true)]
public DateTime? Timestamp { get; set; }
public String Weather { get; set; }
public int Difficulty { get; set; }
public bool Reviewed { get; set; }
}
My View row looks like this
#model IList<ActivityModel>
#for (int i = 0; i < Model.Count; i++)
{
if (!(bool)Model[i].Reviewed)
{
<tr>
#using (Html.BeginForm("Accept", "Overview", FormMethod.Post, new { activityModel = Model }))
{
#Html.AntiForgeryToken()
<td>
#Html.DisplayFor(m => Model[i].Timestamp)
#Html.HiddenFor(m => Model[i].Timestamp)
</td>
<td>
#Html.DisplayFor(m => Model[i].Nick)
#Html.HiddenFor(m => Model[i].Nick)
</td>
<td>
#Html.DisplayFor(m => Model[i].TripName)
#Html.HiddenFor(m => Model[i].TripName)
</td>
<td>
#Html.DisplayFor(m => Model[i].FromTime)
#Html.HiddenFor(m => Model[i].FromTime)
</td>
<td>
#Html.DisplayFor(m => Model[i].FromPlace)
#Html.HiddenFor(m => Model[i].FromPlace)
</td>
<td>
#Html.DisplayFor(m => Model[i].ToTime)
#Html.HiddenFor(m => Model[i].ToTime)
</td>
<td>
#Html.DisplayFor(m => Model[i].ToPlace)
#Html.HiddenFor(m => Model[i].ToPlace)
</td>
<td>
#Html.TextBoxFor(m => Model[i].Difficulty, new { id = "Difficulty" })
<--!#Html.HiddenFor(m => Model[i].Difficulty)--!>
<div style="visibility:hidden">
#Html.HiddenFor(m => Model[i].Id)
#Html.HiddenFor(m => Model[i].Reviewed)
#Html.HiddenFor(m => Model[i].activityType)
#Html.HiddenFor(m => Model[i].Weather)
</div>
</td>
<td>
<input type="submit" name="btn_accept" value="Accept" />
</td>
}
</tr>
}
}
And my Controller is this
public ActionResult Index()
{
List<ActivityModel> activities_web = new List<ActivityModel>();
//someLogicForFilling
return View(activities_web);
}
[HttpPost]
[ValidateAntiForgeryToken()]
public ActionResult Accept(ActivityModel activityModel)
{
return RedirectToAction("Index");
}
And the problem is that in ActionResult Accept is ActivityModel always NULL empty 0 ...
I really spent whole day on it for now and I don't know where is the problem i tried almost 20 solutions and nothing worked for me..
Rendering the page and showing all values is OK but when I try to POST them like nothing is posted.
Model binding will work when the posted data matches with the property structure of the class you are using as your HttpPost action method parameter.
Your current view code is generating HTML code like this
<input name="[0].Id" type="hidden" value="101">
<input name="[1].Id" type="hidden" value="202">
Assuming you have 2 items in the collection you are passing to the view.
You can see that the name attribute value is [0].Id and [1].Id. But your Accept action method's parameter is a single ActivityModel object and the property names are like Id, FromPlace etc. For model binding to work, the names of your input and the property name should match.
You can rewrite your view code to generate the correct name attribute value for your hidden inputs. You may simply use the pure HTML code to create the hidden input and set the value from your model item or use the Html.HiddenFor helper method.
#foreach (var item in Model.Where(a => a.Reviewed == false))
{
<input type="hidden" name="FromPlace" value="#item.FromPlace" />
#Html.Hidden("ToPlace", item.ToPlace)
#Html.Hidden(nameof(ActivityModel.Id), item.Id)
}
Here is the full code (excluded some properties to save space).
#model IList<ActivityModel>
<table class="table table-striped">
#foreach (var item in Model.Where(a => a.Reviewed == false))
{
using (Html.BeginForm("Accept", "Process", FormMethod.Post))
{
#Html.AntiForgeryToken()
<tr>
<td>
#Html.DisplayFor(m => item.FromPlace)
#Html.Hidden("FromPlace", item.FromPlace)
</td>
<td>
#Html.DisplayFor(m => item.ToPlace)
#Html.Hidden("ToPlace", item.ToPlace)
</td>
<td>
#Html.Hidden(nameof(ActivityModel.Id),item.Id)
<input type="submit" name="btn_accept" value="Accept" />
</td>
</tr>
}
}
</table>
But ideally, if you are trying to update the status of a single entity /record, all you need is the Id (primary key) of that record. that means, you really do not need all the properties populated. In that case, you need the hidden input element for the Id property value. You can always query the db to get the full entity using this Id if needed. ( Never trust data coming from client)

How can i bind a nested list to view with groupby() MVC 4

I have a list of model like this
public class Group
{
public string Name { get; set; }
public string GroupID { get; set; }
}
In controller:
public ActionResult Index()
{
return View(ListGroup);
}
[HttpPost]
public ActionResult Index(List<Group> listModel) {
#ViewBag.Success = "Update Suceess";
return View(listModel);
}
[HttpPost]
public ActionResult Search(Group ModelSearch) {
List<Group> listResult = ListGroup.Where(m=>m.GroupID == ModelSearch.GroupID).ToList();
if (ModelSearch.GroupID == null) return View("Index", ListGroup);
return View("Index", listResult);
}
In view i group the list as below:
#foreach (var items in Model.GroupBy(m => m.GroupID).Select(g => g.ToList()))
{
<tr>
<td colspan="2">#items.ElementAt(0).GroupID</td>
</tr>
foreach (var item in items)
{
<tr>
<td>#Html.TextBoxFor(m => item.GroupID)</td>
<td>#Html.TextBoxFor(m => item.Name)</td>
</tr>
}
}
It can display as groups well but i cannot get value back to controller after button submit was fired.
How can i bind model to view that controller can understand and receive data from view.
according to my understanding,
you need to use a for loop rather than a foreach this will index your collections, by doing this modelbinder will work.
#for(var i = 0; i < items; i++)
{
<tr>
<td>#Html.TextBoxFor(m => m[i].GroupID)</td>
<td>#Html.TextBoxFor(m => m[i].Name)</td>
</tr>
}

MVC 4 Model is empty when I try to submit a form

I have two forms on one View executing two separate Action methods in one Controller.
The first form (frmRefresh) is responsible for getting data and displaying it on the form so the user can pick certain checkboxes. Once submitted, the data is returned just fine in the ViewModel and is properly displayed on the form. 11 records for the Templates and 3 records for the Guarantors are displyaed as checkboxes on the form.
The second form (frmProcess), is responsible for taking the data on the form (that came back from the first post above). The user makes selections on the screen and processes it against some logic in the Controller. I have List objects in the model and don't suppose I can use the FormCollection to process the data because of the complex objects. Basically, they are a collection of checkboxes. I really need to use the data that should be submitted in the Model because of processing in the Controller for that data.
When submitting the second form, I realize that the loanid & ddl will not be available unless I put them in a hidden field (because they are in a separate form) --- that's fine. What I'm having a great deal of difficulty in understanding is when I submit the second form (frmProcess), why doesn't the model view binder take the data from the form, put it in the model and submit it to my GeneratePDF action method.?
Number one, I really need some help in understanding why this is happening and number two, I really need a soltution which takes my model data from the form to the action method and processes it. As you can see in the Controller, at the end of the code, I'm enumerating the Templates in the ViewModel to process the data.
Please help, as I am totally stuck on this at work and they are depending on me for this. I just don't get why the model binder doesn't take the values on the form and submit it to the action method for processing. It appears I'm missing something to allow the data to get back into the Model upon submission.
Below is my pertinent code:
ViedwModel
public partial class ViewModelTemplate_Guarantors
{
public int SelectedTemplateId { get; set; }
public IEnumerable<PDFTemplate> Templates { get; set; }
public int SelectedGuarantorId { get; set; }
public IEnumerable<tGuarantor> Guarantors { get; set; }
public string LoanId { get; set; }
public string SelectedDeptText { get; set; }
public string SelectedDeptValue { get; set; }
public string LoanType { get; set; }
public bool ShowTemps { get; set; }
public string Error { get; set; }
public string ErrorT { get; set; }
public string ErrorG { get; set; }
public bool ShowGeneratePDFBtn { get; set; }
}
View
#model PDFConverterModel.ViewModels.ViewModelTemplate_Guarantors
#{
ViewBag.Title = "BHG :: PDF Generator";
}
<h2>#ViewBag.Message</h2>
<div>
<table style="width: 1000px">
<tr>
<td colspan="5">
<img alt="BHG Logo" src="~/Images/logo.gif" />
</td>
</tr>
#using (Html.BeginForm("Refresh", "Home", FormMethod.Post, new { id = "frmRefresh" })) { <tr>
<td>
#*#(Html.Kendo().NumericTextBox<int>()
.Name("txtLoanID")
.Placeholder("Enter numeric value")
)*#
#Html.LabelFor(model => model.LoanId)
#Html.TextBoxFor(model => model.LoanId)
#Html.ValidationMessageFor(model => model.LoanId)
</tr>
<tr>
<td>#Html.LabelFor(model => model.LoanType)
#Html.TextBox("SBA", "SBA")
#Html.ValidationMessageFor(model => model.LoanType)
#*#Html.TextBoxFor(model => model.LoanType)*#
</td>
<td>
<label for="ddlDept">Department:</label>
#(Html.Kendo().DropDownListFor(model => model.SelectedDeptText)
.Name("ddlDept")
.DataTextField("DepartmentName")
.DataValueField("DepartmentID")
.Events(e => e.Change("Refresh"))
.DataSource(source =>
{
source.Read(read =>
{
read.Action("GetDepartments", "Home");
});
})
)
#Html.ValidationMessageFor(model => model.SelectedDeptText)
</td>
</tr>
<tr>
<td colspan="3">
<input type="submit" id="btnRefresh" value='Refresh' />
</td>
</tr>
}
#using (Html.BeginForm("GeneratePDF", "Home", FormMethod.Post, new { id = "frmProcess" })) { if (Model.ShowGeneratePDFBtn == true)
{
if (Model.ErrorT != string.Empty)
{
<tr>
<td colspan="5">
<u><b>#Html.Label("Templates:")</b></u>
</td>
</tr>
<tr>
#foreach (var item in Model.Templates)
{
<td>
#Html.CheckBoxFor(model => item.IsChecked)
#Html.DisplayFor(model => item.TemplateName)
</td>
}
</tr>
}
else
{
Model.Error = Model.ErrorT;
}
if (Model.ErrorG != string.Empty)
{
<tr>
<td colspan="5">
<u><b>#Html.Label("Guarantors:")</b></u>
</td>
</tr>
<tr>
#foreach (var item in Model.Guarantors)
{
<td>
#Html.CheckBoxFor(model => item.isChecked)
#Html.DisplayFor(model => item.GuarantorFirstName) #Html.DisplayFor(model => item.GuarantorLastName)
</td>
}
</tr>
}
else
{
Model.Error = Model.ErrorG;
}
<tr>
<td>
<input type="submit" id="btnGeneratePDF" value='Generate PDF' />
</td>
</tr>
<tr>
<td colspan="5">
#Model.Error
</td>
</tr>
}
} </table>
</div>
<script type="text/javascript">
$('btnRefresh').on('click', '#btnRefresh', function () {
Refresh();
});
function Refresh() {
var LoanID = $("#LoanID").val();
if (LoanID != "") {
document.forms["frmTemps"].submit();
}
}
</script>
Controller
public ActionResult Index(ViewModelTemplate_Guarantors model)
{
ViewBag.Error = "";
model.ShowGeneratePDFBtn = false;
return View("Index", model);
}
// used for the first form "frmRefresh" [HttpPost] public ActionResult Refresh(ViewModelTemplate_Guarantors model) {
try
{
model.Error = string.Empty;
bool dbHasRows = db.ChkLoanFields(Convert.ToInt32(model.LoanId));
if (!dbHasRows)
{
model.ShowGeneratePDFBtn = false;
model.Error = "Details not available for this LoanId.";
return View("Index",model);
}
else
{
int TemplateCnt = 0;
int GuarantorCnt = 0;
//todo - modify 2nd & 3rd parms instead of hardcoding
ViewModelTemplate_Guarantors tg = db.SelectViewModelTemplate_Guarantors(Convert.ToInt32(model.LoanId), "All", "All", out TemplateCnt, out GuarantorCnt);
if (TemplateCnt > 0)
model.Templates = tg.Templates;
else
model.ErrorT = "Templates not available for this LoanType.";
if (GuarantorCnt > 0)
model.Guarantors = tg.Guarantors;
else
model.ErrorG = "Guarantors not available for this LoanId.";
model.ShowGeneratePDFBtn = true;
// right before the return here, the model is full of data. return View("Index", model); }
}
catch (Exception ex)
{
throw ex;
}
} [HttpPost] // when I check the data here (via submission from the "frmProcess" form, the model is completely empty, null, etc... WHY???? // i NEED the model data here to perform processing in this action method. public ActionResult GeneratePDF(ViewModelTemplate_Guarantors model) {
try
{
int FolderNo, GuarantorNum = 0;
string Folder, LoanFolder = String.Empty;
string FormId, FormName, GuarantorName = String.Empty;
int LoanId = Convert.ToInt32(model.LoanId);
LoanFolder = LoanId.ToString().PadLeft(8, '0');
//To calculate FolderId based on LoanId
if ((LoanId > 0) && (LoanId < 99000))
{
FolderNo = ((int)(LoanId / 10000) * 10000);
}
else
{
FolderNo = ((int)(LoanId / 1000) * 1000);
}
Folder = ((int)FolderNo).ToString();
Folder = Folder.PadLeft(8, '0');
//todo - 2nd parm SelectedValue of dept
List<sSRPTFundexDocCodes1_Test_Result> sSRPTFundexDocCodes1 = db.GetFormValues(Convert.ToInt32(model.LoanId), (model.SelectedDeptValue));
if (sSRPTFundexDocCodes1 != null)
{
foreach (PDFTemplate template in model.Templates) {
if (template.IsChecked == true) {
TemplateName not showing up in model after post.
This works fine... The values (the checkboxes and the corresponding names are displyaed on the form.
However, when posting the GeneratePDF button, all I see in the model is if the Checkbox is checked (which is great). After playing around with many of the following statements: (ValueFor, DisplayFor, LabelFor, EditorFor, etc), the value coming back for the Template name is blank. I need the name of the template that was checked in correspondance with the checkbox.
#Html.ValueFor(model => Model.Templates[i].TemplateName)
How can I accomplish this? Thanks ahead of time... Below is my updated code.
ViewModel public partial class ViewModelTemplate_Guarantors
{
public ViewModelTemplate_Guarantors()
{
Templates = new List<PDFTemplate>();
Guarantors = new List<tGuarantor>();
}
public int SelectedTemplateId { get; set; }
public List<PDFTemplate> Templates { get; set; }
public int SelectedGuarantorId { get; set; }
public List<tGuarantor> Guarantors { get; set; }
public string LoanId { get; set; }
public string SelectedDeptText { get; set; }
public string SelectedDeptValue { get; set; }
public string LoanType { get; set; }
public string Error { get; set; }
public string ErrorT { get; set; }
public string ErrorG { get; set; }
public bool ShowGeneratePDFBtn { get; set; }
}
Pertinet part of View:
if (Model.ShowGeneratePDFBtn == true)
{
if (Model.ErrorT == string.Empty)
{
<tr>
<td colspan="5">
<u><b>#Html.Label("Templates:")</b></u>
</td>
</tr>
<tr>
#for (int i = 0; i < Model.Templates.Count; i++)
{
<td>
#Html.CheckBoxFor(model => Model.Templates[i].IsChecked)
#Html.ValueFor(model => Model.Templates[i].TemplateName) </td>
}
</tr>
}
else
{
<tr>
<td>
<b>#Html.DisplayFor(model => Model.ErrorT)</b>
</td>
</tr>
}
if (Model.ErrorG == string.Empty)
{
<tr>
<td colspan="5">
<u><b>#Html.Label("Guarantors:")</b></u>
</td>
</tr>
<tr>
#for (int i = 0; i < Model.Guarantors.Count; i++)
{
<td>
#Html.CheckBoxFor(model => Model.Guarantors[i].isChecked)
#Html.ValueFor(model => Model.Guarantors[i].GuarantorFirstName) #Html.ValueFor(model => Model.Guarantors[i].GuarantorLastName) </td>
}
</tr>
}
else
{
<tr>
<td>
<b>#Html.DisplayFor(model => Model.ErrorG)</b>
</td>
</tr>
}
}
<tr>
<td colspan="3">
<input type="submit" name="submitbutton" id="btnRefresh" value='Refresh' />
</td>
#if (Model.ShowGeneratePDFBtn == true)
{
<td>
<input type="submit" name="submitbutton" id="btnGeneratePDF" value='Generate PDF' />
</td>
}
</tr>
<tr>
<td colspan="5">
#Model.Error
</td>
</tr>
Controller:
public ActionResult ProcessForm(string submitbutton, ViewModelTemplate_Guarantors model, FormCollection collection)
Basically, again it's working fine. When the form posts using the Generate PDF button, I get the checked value of each checkbox, but not the name of the template in the Model.
Am I missing something here???
The form before I submit is basically like below. It's the name of the checkbox (Form4) that I'm missing as a TemplateID in my Model once I get into the ActionResult.
public ActionResult ProcessForm(string submitbutton, ViewModelTemplate_Guarantors model, FormCollection collection)
checkbox (checked) Form4
#for (int i = 0; i < Model.Templates.Count; i++)
{
<td>
#Html.CheckBoxFor(model => Model.Templates[i].IsChecked)
#Html.DisplayFor(model => Model.Templates[i].TemplateName)
</td>
}
As I mentioned in my comment. The model binder cannot bind to an IEnumerable.
Your Model should look like this:
public partial class ViewModelTemplate_Guarantors
{
public ViewModelTemplate_Guarantors() {
Templates = new List<PDFTemplate>(); // These are important, the model binder
Guarantors = new List<tGuarantor>(); // will not instantiate nested classes
}
public int SelectedTemplateId { get; set; }
public List<PDFTemplate> Templates { get; set; }
public int SelectedGuarantorId { get; set; }
public List<tGuarantor> Guarantors { get; set; }
...
}
Further, your view should look like this:
...
#for(int i = 0; i < Model.Templates.Count; i++) // should really use label, not display
{
<td>
#Html.CheckBoxFor(model => Model.Templates[i].IsChecked)
#Html.DisplayFor(model => Model.Templates[i].TemplateName)
</td>
}
...
#for(int i = 0; i < Model.Guarantors.Count; i++)
{
<td>
#Html.CheckBoxFor(model => Model.Guarantors[i].isChecked)
#Html.DisplayFor(model => Model.Gurantors[i].GuarantorFirstName) #Html.DisplayFor(model => Model.Guarantors[i].GuarantorLastName)
</td>
}
...
Although a better choice would be to use an EditorTemplate and instead do this:
...
#Html.EditorFor(m => m.Templates)
...
#Html.EditorFor(m => m.Guarantors)
...
Then create a folder in ~/Views/Shared called EditorTemplates, and then create two files called Templates.cshtml and Guarantors.cshtml.
In those files you would do this:
#model PDFTemplate
<td>
#Html.CheckBoxFor(model => model.IsChecked)
#Html.DisplayFor(model => model.TemplateName)
</td>
and
#model Guarantors
<td>
#Html.CheckBoxFor(model => model.isChecked)
#Html.DisplayFor(model => model.GuarantorFirstName) #Html.DisplayFor(model => model.GuarantorLastName)
</td>
The editor templates will automatically iterate over the collection and will account for the correct naming format to make the model binder understand it's a collection.

ViewModel collection property lost values after posting back to controller action in MVC 3

I have my view models :
public class POReceiptViewModel
{
public virtual int PONumber { get; set; }
public virtual string VendorCode { get; set; }
public virtual IList<POReceiptItemViewModel> POReceiptItems { get; set; }
public POReceiptViewModel()
{
POReceiptItems = new List<POReceiptItemViewModel>();
}
}
public class POReceiptItemViewModel
{
public virtual string ItemCode { get; set; }
public virtual string ItemDesription { get; set; }
public virtual decimal OrderedQuantity { get; set; }
public virtual decimal ReceivedQuantity { get; set; }
public virtual DateTime ReceivedDate { get; set; }
public POReceiptItemViewModel()
{
ReceivedDate = DateTime.Now;
}
}
Then my controller has two actions, one get and one post:
public ActionResult CreatePOReceipt(int poNumber)
{
PurchaseOrder po = PurchasingService.GetPurchaseOrder(poNumber);
POReceiptViewModel poReceiptViewModel = ModelBuilder.POToPOReceiptViewModel(po);
return View("CreatePOReceipt", poReceiptViewModel);
}
[HttpPost]
public ActionResult CreatePOReceipt(POReceiptViewModel poReceiptViewModel)
{
// Here the problem goes. The items in the poReceiptViewModel.POReceiptItems has lost. the count became zero.
return View("Index");
}
And in my View, I can display the model properly and by using #Html.HiddenFor<> I can persist view model data as I wanted to. But not on the List<> navigation property.
#model POReceiptViewModel
#using (Html.BeginForm())
{
<fieldset>
<legend>Purchase Order</legend>
<label>For PO # :</label>
#Html.HiddenFor(m => m.PONumber)
#Html.DisplayTextFor(m => m.PONumber)
<label>Vendor Code :</label>
#Html.HiddenFor(m => m.VendorCode)
#Html.DisplayTextFor(m => m.VendorCode)
</fieldset>
<fieldset>
<legend>Received Items</legend>
<table class="tbl" id="tbl">
<thead>
<tr>
<th>Item Code</th><th>Item Description</th><th>OrderedQuantity</th><th>Received Quantity</th><th>Received Date</th>
</tr>
</thead>
<tbody>
#Html.HiddenFor(m => m.POReceiptItems) // I'm not really sure if this is valid
#if (Model.POReceiptItems.Count > 0)
{
foreach (var item in Model.POReceiptItems)
{
<tr>
<td>#Html.DisplayTextFor(i => item.ItemCode)</td>#Html.HiddenFor(i => item.ItemCode)
<td>#Html.DisplayTextFor(i => item.ItemDesription)</td>#Html.HiddenFor(i => item.ItemDesription)
<td>#Html.DisplayTextFor(i => item.OrderedQuantity)</td>#Html.HiddenFor(i => item.OrderedQuantity)
<td>#Html.TextBoxFor(i => item.ReceivedQuantity)</td>
<td>#Html.TextBoxFor(i => item.ReceivedDate)</td>
</tr>
}
}
</tbody>
</table>
</fieldset>
<input type="submit" name="Received" value="Received" />
}
PROBLEM:
POReceiptItems lost when the form submitted. As much as possible I don't want to use TempData["POReceiptItems"] = Model.POReceiptItems but even if I use it, the value entered into ReceivedQuantity and ReceivedDate are not save into the TempData.
Thanks in advance!
try
#for (int i = 0; i < Model.POReceiptItems.Count(); i++)
{
<tr>
<td>#Html.DisplayTextFor(m => m.POReceiptItems[i].ItemCode)</td>#Html.HiddenFor(m => m.POReceiptItems[i].ItemCode)
<td>#Html.DisplayTextFor(m => m.POReceiptItems[i].ItemDesription)</td>#Html.HiddenFor(m => m.POReceiptItems.ItemDesription) <td>#Html.DisplayTextFor(m => m.POReceiptItems[i].OrderedQuantity)</td>#Html.HiddenFor(m => m.POReceiptItems[i].OrderedQuantity)
<td>#Html.TextBoxFor(m => m.POReceiptItems[i].ReceivedQuantity)</td>
<td>#Html.TextBoxFor(m => m.POReceiptItems[i].ReceivedDate)</td>
</tr>
}
also read this blog post to understand how model binding to a list works
You lose your list because MVC don't handle the List the way you think.
You should use BeginCollectionItem look at this post
I had a similar problem, the "List" attribute returned without values(count = 0), I tried different ways and answers and nither works.
Then I tried by myself and now it is working, this is my solution:
I send an object with some normal attributes and a "List", after that I used the normal attributes and my "list" in a For.
In my controller (Post ActionResult), in the parameters section I added two parameters, my original object and my "List" as second parameter and it works!!!
I hope this helps you and others with similar problems.

Resources