By VansFannel


2015-03-20 07:27:29 8 Comments

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 comments

@Mohamed Nagieb 2018-10-15 22:48:44

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.

@Mahesh Pullagura 2016-08-13 18:33:49

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

}

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

@Sourav Mondal 2016-06-29 04:52:18

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);
    }
}

@user3559349 2015-03-20 07:50:44

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());
});

@VansFannel 2015-03-20 07:53:04

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

@user3559349 2015-03-20 07:54:23

See update to some techniques to do this (and it does not need IEnumerable<T> - it can just as easily be IList<T>)

@user3559349 2015-03-20 08:04:12

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.

@user3559349 2015-03-20 08:06:35

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.

@VansFannel 2015-03-20 08:22:49

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.

@user3559349 2015-03-20 08:27:31

Yes, but it you are also wanting to delete items as indicated in your script, you also need the special input for the index property. By default the ModelBinder will only bind collections where the indexer starts at zero and is consecutive - test it by deleting the first item and the collection will be empty (adding the hidden input for BatchProducts.Index overrides that). Also do not base you indexer on the count of the items - if you delete one item, then add another you will have duplicates and binding will also fail

@user3559349 2015-03-20 08:46:32

Answer updated with suggested code to allow dynamically adding and deleting items

@VansFannel 2015-03-20 09:38:23

Thanks a lot for your time an effort. You've helped me a lot.

@Aizen 2015-04-18 17:40:42

Finally, after Learning ViewModels I was able to use this.

@Andre 2015-03-20 07:44:50

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.

@VansFannel 2015-03-20 07:47:26

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

@Andre 2015-03-20 08:35:24

then change your model to IEnumerable

Related Questions

Sponsored Content

44 Answered Questions

[SOLVED] How do I create an Excel (.XLS and .XLSX) file in C# without installing Microsoft Office?

  • 2008-09-29 22:30:28
  • mistrmark
  • 1050090 View
  • 1809 Score
  • 44 Answer
  • Tags:   c# .net excel file-io

71 Answered Questions

[SOLVED] How do you disable browser Autocomplete on web form field / input tag?

19 Answered Questions

[SOLVED] How do I modify the URL without reloading the page?

17 Answered Questions

[SOLVED] How to prevent buttons from submitting forms

39 Answered Questions

30 Answered Questions

[SOLVED] Prevent users from submitting a form by hitting Enter

29 Answered Questions

[SOLVED] JavaScript post request like a form submit

3 Answered Questions

[SOLVED] Cannot display HTML string

27 Answered Questions

[SOLVED] I need an unordered list without any bullets

  • 2009-06-22 13:57:53
  • praveenjayapal
  • 2011776 View
  • 2412 Score
  • 27 Answer
  • Tags:   html css

9 Answered Questions

[SOLVED] What does enctype='multipart/form-data' mean?

Sponsored Content