user2695433
user2695433

Reputation: 2153

Submit data with dynamically added partial view to the controller using ViewModels not working

I'm adding dynamically items to an Enquiry form. Used partial view to for adding/deleting the items but while submitting the main view the values are not bound. My question is how to do the same.

Have checked couple of similar questions here and here But could not find what's missing .

Using 2 ViewModels , for Main View ( Enquiry) and for partial view ( LineItems) and used BeginCollectionItem for dynamically adding items.

Code:

ViewModels

public class EnquiryVM
    {
        public int ID { get; set; }

        [Required]
        public string EnquiryNumber { get; set; }
        public int ClientID { get; set; }
        public IEnumerable<SelectListItem> Clients { get; set; }
        public Client Client { get; set; }
        public int ItemID { get; set; }
        public List<EnquiryLineItem> LineItems { get; set; }

    }
 public class EnquiryLineItemVM
    {
        public int ID { get; set; }
        [Required]
        public string ItemDesc { get; set; }
        public int Quantity { get; set; }
        public int ManufacturerId { get; set; }
        public IEnumerable<SelectListItem> ManufacturerList { get; set; }
    }

Views : Main:

@model ViewModel.EnquiryVM

@using (Html.BeginForm("Create", "Enquiries", FormMethod.Post))
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">

        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })



        <div class="form-group">
            @Html.LabelFor(model => model.EnquiryNumber, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-3">
                @Html.EditorFor(model => model.EnquiryNumber, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.EnquiryNumber, "", new { @class = "text-danger" })
            </div>
        </div>





        <div class="form-group">
            @Html.LabelFor(model => model.ClientID, "Client", htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-3">

                @Html.DropDownListFor(u => u.ClientID, (IEnumerable<SelectListItem>)Model.Clients, "--Select--")
                @Html.ValidationMessageFor(model => model.ClientID, "", new { @class = "text-danger" })
            </div>
        </div>

         <div id="LineItems">
          //  @using (Html.BeginForm()) // do we require again here since this will be like nested form? tested commenting still not working
           // {
                <div id="editorRowsLineitems">
                    @foreach (var item in Model.LineItems)
                    {
                        @Html.Partial("_CreateEnquiryItem", item)
                    }
                </div>
                @Html.ActionLink("Add Items", "CreateLineItem", null, new { id = "addItem", @class = "button" });
          //  }
        </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>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
<script type="text/javascript">
    $(function () {
        $('#addItem').on('click', function () {
            $.ajax({
                url: '@Url.Action("CreateLineItem")',
                    cache: false,
                    success: function (html) {
                        $("#editorRowsLineitems").append(html);

                        $("form").removeData("validator");
                        $("form").removeData("unobtrusiveValidation");
                        $.validator.unobtrusive.parse("form");
                    }
                });
                return false;
            });
        $('#editorRowsLineitems').on('click', '.deleteRow', function () {
                $(this).closest('.editorRow').remove();
            });
        $('form').data('validator', null);
        $.validator.unobtrusive.parse($('form'));
    });


</script>
}

partial view :

@model ViewModels.EnquiryLineItemVM

<div class="editorRow">
    @using (Html.BeginCollectionItem("ItemList"))
    {
        <table class="table">

            <tr>
                <td>
                    @Html.EditorFor(model => model.ItemDesc)

                </td>
                <td>
                    @Html.EditorFor(model => model.Quantity)

                </td>

                <td>
                    @Html.DropDownListFor(model => model.ManufacturerId, Model.ManufacturerList, "--Please Select--")

                </td>
                <td>

                    <a href="#" class="deleteRow">Delete</a>
                </td>
            </tr>
        </table>

    }

Controller :

 public ActionResult Create()
        {
            var viewModel = GetAllCategories();
            return View(viewModel);
        }
  private EnquiryVM GetAllCategories()
        {
            var model = new EnquiryVM();
            var clients = db.Clients.ToList();
            model.Clients = clients.Select(s => new SelectListItem
            {
                Value = s.ID.ToString(),
                Text = s.Name
            });

            var LineItems = new List<EnquiryLineItem>();
            model.LineItems = LineItems;

           return model;
        }
 [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create( EnquiryVM enquiryVM)
        {
            var enquiry = new Enquiry();
            enquiry.EnquiryNumber = enquiryVM.EnquiryNumber;
            enquiry.ClientID = enquiryVM.ClientID;
            enquiry.EnquiryLineItems = enquiryVM.LineItems; //line items are null
            if (ModelState.IsValid)
            {
                db.Enquiries.Add(enquiry);
                enquiryVM.ID = enquiry.ID;
                foreach (var item in enquiry.EnquiryLineItems)
                {
                    item.EnquiryID = enquiryVM.ID;
                    db.EnquiryLineItems.Add(item);
                }

                db.SaveChanges();
                return RedirectToAction("Index");
            }

            var viewModel = GetAllCategories();
            return View(enquiryVM);
        }

How shall I map the dynamically added row's values to the ViewModel ( EnquiryVM ) so that I can insert it into the DB. Thanks for your patience and time.

Upvotes: 1

Views: 1742

Answers (1)

user3559349
user3559349

Reputation:

The name of your collection property is LineItems, therefore your code to generate its controls needs to be

@using (Html.BeginCollectionItem("LineItems")) // not ..("ItemList")
{
    ....
}

so that it generates inputs with name="LineItems[xxxx].ItemDesc" etc, rather than your current use which generates name="ItemList[xxxx].ItemDesc" (where xxxx is the Guid)

As a side note, the code in your POST method will throw an exception if ModelState is invalid because you return the view and have not repopulated the IEnumerable<SelectListItem> Clients property. Refer The ViewData item that has the key 'XXX' is of type 'System.Int32' but must be of type 'IEnumerable' for a detailed explanation.

In addition, the final 2 lines of your script to add items ($('form').data('validator', null); $.validator.unobtrusive.parse($('form')); should be removed (reparsing the validator is expensive and your doing it twice - once before you add the html (the 2 lines above) and once after you add the html

Upvotes: 2

Related Questions