40

I'm developing an ASP.NET MVC 5 web with C# and .NET Framework 4.5.1.

I have this form in a cshtml file:

@model MyProduct.Web.API.Models.ConnectBatchProductViewModel @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Create</title> </head> <body> @if (@Model != null) { <h4>Producto: @Model.Product.ProductCode, Cantidad: @Model.ExternalCodesForThisProduct</h4> using (Html.BeginForm("Save", "ConnectBatchProduct", FormMethod.Post)) { @Html.HiddenFor(model => model.Product.Id, new { @id = "productId", @Name = "productId" }); <div> <table id ="batchTable" class="order-list"> <thead> <tr> <td>Cantidad</td> <td>Lote</td> </tr> </thead> <tbody> <tr> <td>@Html.TextBox("ConnectBatchProductViewModel.BatchProducts[0].Quantity")</td> <td>@Html.TextBox("ConnectBatchProductViewModel.BatchProducts[0].BatchName")</td> <td><a class="deleteRow"></a></td> </tr> </tbody> <tfoot> <tr> <td colspan="5" style="text-align: left;"> <input type="button" id="addrow" value="Add Row" /> </td> </tr> </tfoot> </table> </div> <p><input type="submit" value="Seleccionar" /></p> } } else { <div>Error.</div> } <script src="~/Scripts/jquery-2.1.3.min.js"></script> <script src="~/js/createBatches.js"></script> <!-- Resource jQuery --> </body> </html> 

And this is the action method:

[HttpPost] public ActionResult Save(FormCollection form) { return null; } 

And the two ViewModel:

public class BatchProductViewModel { public int Quantity { get; set; } public string BatchName { get; set; } } public class ConnectBatchProductViewModel { public Models.Products Product { get; set; } public int ExternalCodesForThisProduct { get; set; } public IEnumerable<BatchProductViewModel> BatchProducts { get; set; } } 

But I get this in FormCollection form var: enter image description here

But I want to get an IEnumerable<BatchProductViewModel> model:

public ActionResult Save(int productId, IEnumerable<BatchProductViewModel> model); 

If I use the above method signature both parameters are null.

I want an IEnumerable because user is going to add more rows dynamically using jQuery.

This is jQuery script:

jQuery(document).ready(function ($) { var counter = 0; $("#addrow").on("click", function () { counter = $('#batchTable tr').length - 2; var newRow = $("<tr>"); var cols = ""; var quantity = 'ConnectBatchProductViewModel.BatchProducts[0].Quantity'.replace(/\[.{1}\]/, '[' + counter + ']'); var batchName = 'ConnectBatchProductViewModel.BatchProducts[0].BatchName'.replace(/\[.{1}\]/, '[' + counter + ']'); cols += '<td><input type="text" name="' + quantity + '"/></td>'; cols += '<td><input type="text" name="' + batchName + '"/></td>'; cols += '<td><input type="button" class="ibtnDel" value="Delete"></td>'; newRow.append(cols); $("table.order-list").append(newRow); counter++; }); $("table.order-list").on("click", ".ibtnDel", function (event) { $(this).closest("tr").remove(); counter -= 1 $('#addrow').attr('disabled', false).prop('value', "Add Row"); }); }); 

Any idea?

I have checked this SO answer, and this article but I don't get my code working.

5
  • Please post the code for BatchProductViewModel as well... Also, in your action method, are you sure your intent was to use BatchProductViewModel and not ConnectBatchProductViewModel? Commented Mar 20, 2015 at 7:37
  • @Ruslan Question updated. Commented Mar 20, 2015 at 7:38
  • Your model in the view is ConnectBatchProductViewModel If you want to generate a view for a collection of BatchProductViewModel then you view needs to be IEnumerable<BatchProductViewModel> and the POST method parameter needs to be the same (don't use FormCollection) and the controls for BatchProductViewModel need to be generated in a for loop Commented Mar 20, 2015 at 7:42
  • And int productId will never be bound because the name of your control is Product.Id (not productId). If you change the method to public ActionResult Save(ConnectBatchProductViewModel model) you will see that it is correctly bound Commented Mar 20, 2015 at 7:45
  • I have updated my question. I have changed the view model and I getting the same result. I want an IEnumerable because I want to add more rows dynamically using jQuery. Commented Mar 20, 2015 at 7:46

5 Answers 5

32

You need to generate the controls for the collection in a for loop so they are correctly named with indexers (note that property BatchProducts needs to be IList<BatchProductViewModel>

@using (Html.BeginForm("Save", "ConnectBatchProduct", FormMethod.Post)) { .... <table> .... @for(int i = 0; i < Model.BatchProducts.Count; i++) { <tr> <td>@Html.TextBoxFor(m => m.BatchProducts[i].Quantity)</td> <td>@Html.TextBoxFor(m => m.BatchProducts[i].BatchName)</td> <td> // add the following to allow for dynamically deleting items in the view <input type="hidden" name="BatchProducts.Index" value="@i" /> <a class="deleteRow"></a> </td> </tr> } .... </table> .... } 

Then the POST method needs to be

public ActionResult Save(ConnectBatchProductViewModel model) { .... } 

Edit

Note: Further to your edit, if you want to dynamically add and remove BatchProductViewModel items in he view, you will need to use the BeginCollectionItem helper or a html template as discussed in this answer

The template to dynamically add new items would be

<div id="NewBatchProduct" style="display:none"> <tr> <td><input type="text" name="BatchProducts[#].Quantity" value /></td> <td><input type="text" name="BatchProducts[#].BatchName" value /></td> <td> <input type="hidden" name="BatchProducts.Index" value ="%"/> <a class="deleteRow"></a> </td> </tr> </div> 

Note the dummy indexers and the non-matching value for the hidden input prevents this template posting back.

Then the script to add a new BatchProducts would be

$("#addrow").click(function() { var index = (new Date()).getTime(); // unique indexer var clone = $('#NewBatchProduct').clone(); // clone the BatchProducts item // Update the index of the clone clone.html($(clone).html().replace(/\[#\]/g, '[' + index + ']')); clone.html($(clone).html().replace(/"%"/g, '"' + index + '"')); $("table.order-list").append(clone.html()); }); 
Sign up to request clarification or add additional context in comments.

9 Comments

I want an IEnumerable because user is going to add more rows dynamically using jQuery.
See update to some techniques to do this (and it does not need IEnumerable<T> - it can just as easily be IList<T>)
Note that your model does not have a property named ConnectBatchProductViewModel so ConnectBatchProductViewModel.BatchProducts[#].Quantity wont bind to anything. It needs to be BatchProducts[#].Quantity in order to bind.
Next, you don't generate a <input name="BatchProducts.Index" value="#" /> control for each item, so as soon as you delete an item, binding will fail.
Without adding BeginCollectionItem and using <td>@Html.TextBox("BatchProducts[0].Quantity")</td> it works. I get a all data correctly inside the ConnectBatchProductViewModel model parameter. But if I have three rows and user deletes second row, I don't get all data in model parameter.
|
0

In your Post Methode you receive "MyProduct.Web.API.Models.ConnectBatchProductViewModel" as Parameter.
Use the existing model for the Post methode.

Why do you want a IEnumerable from your model? there is only one available including the id in the model.

2 Comments

I want an IEnumerable because I want to add more rows dynamically using jQuery.
then change your model to IEnumerable
0

you can visit this article for complete source code with a video tutorial.

you have to create an action first, from where we can pass the list of object

[HttpGet] public ActionResult Index() { List<Contact> model = new List<Contact>(); using (MyDatabaseEntities dc = new MyDatabaseEntities()) { model = dc.Contacts.ToList(); } return View(model); } 

then we need to create a view for that action

@model List<UpdateMultiRecord.Contact> @{ ViewBag.Title = "Update multiple row at once Using MVC 4 and EF "; } @using (@Html.BeginForm("Index","Home", FormMethod.Post)) { <table> <tr> <th></th> <th>Contact Person</th> <th>Contact No</th> <th>Email ID</th> </tr> @for (int i = 0; i < Model.Count; i++) { <tr> <td> @Html.HiddenFor(model => model[i].ContactID)</td> <td>@Html.EditorFor(model => model[i].ContactPerson)</td> <td>@Html.EditorFor(model => model[i].Contactno)</td> <td>@Html.EditorFor(model => model[i].EmailID)</td> </tr> } </table> <p><input type="submit" value="Save" /></p> <p style="color:green; font-size:12px;"> @ViewBag.Message </p> } @section Scripts{ @Scripts.Render("~/bundles/jqueryval") } 

and then we have to write code for save the list of object to the database

[HttpPost] public ActionResult Index(List<Contact> list) { if (ModelState.IsValid) { using (MyDatabaseEntities dc = new MyDatabaseEntities()) { foreach (var i in list) { var c = dc.Contacts.Where(a =>a.ContactID.Equals(i.ContactID)).FirstOrDefault(); if (c != null) { c.ContactPerson = i.ContactPerson; c.Contactno = i.Contactno; c.EmailID = i.EmailID; } } dc.SaveChanges(); } ViewBag.Message = "Successfully Updated."; return View(list); } else { ViewBag.Message = "Failed ! Please try again."; return View(list); } } 

Comments

0

using(Html.BeginForm()) { // code here }

While to Post form Data all tags must be included form tag.

Comments

0

Following the principle of DRY, you can create one EditorTemplate for that purpose. Steps:

1- In Views > Shared > Create new folder named (EditorTemplates)

2- Create a view inside your newly created EditorTemplates folder , the view's model should be BatchProductViewModel according to the OP example. Place your code inside the Editor view. No loop or index is required.

An EditorTemplate will act similar to a PartialView for every child entity but in a more generic way.

3- In your parent entity's view, call your Editor : @Html.EditorFor(m => m.BatchProducts)

Not only this provides a more organized views, but also let's you re-use the same editor in other views as well.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.