asp.net-mvc-5begincollectionitem

Passing a list to partialview, BeginCollectionItem()


I want to pass a list to PartialView that has BeginCollectionItem(). Here is the code,

InquiryOrderViewModel

public class InquiryOrderViewModel
{
    public InquiryOrder InquiryOrder { get; set; }
    public List<InquiryOrderDetail> InquiryOrderDetails { get; set; }
    public List<InquiryComponentDetail> InquiryComponentDetails { get; set; }        
}

InquiryComponentDetail model

public class InquiryComponentDetail
{
    [Key]
    public int InquiryComponentDetailId { get; set; }

    public int DesignCodeId { get; set; }

    public int QualityReferenceId { get; set; }

    public int Height { get; set; }

    public int Length { get; set; }

    public int GscmComp { get; set; }

    public int Wastage { get; set; }

    public int TotalYarn { get; set; }

    public virtual DesignCodeQltyRef DesignCodeQltyRef { get; set; }

}

InquiryOrderIndex View and the Script to render multiple items at once

@model eKnittingData.InquiryOrderViewModel 

@using (Html.BeginForm("Save", "InquiryOrder"))
{
..........
<div id="cmpDts">
  @foreach (var item in Model.InquiryComponentDetails)
    {

    }
</div>
..........
}

<script>
        var prev;
        $(document).on('focus', '.class03', function () {
            prev = $(this).val();
        }).on('change', '.class03', function () {
            if (prev != "") {
                $.ajax({
                    url: '@Url.Action("ComponentDts", "InquiryOrder")', // dont hard code your url's
                    type: "GET",
                    data: { DesignCdId: $(this).val() }, // pass the selected value
                    success: function (data) {
                        $('.cmpCls').last().replaceWith(data);
                    }
                }); 
            }
            else {
                $.ajax({
                    url: '@Url.Action("ComponentDts", "InquiryOrder")', // dont hard code your url's
                    type: "GET",
                    data: { DesignCdId: $(this).val() }, // pass the selected value
                    success: function (data) {
                            $(".class03 option[value='']").remove();
                            $('#cmpDts').append(data);
                    }
                });
            }
        });
        </script>

The _DetailEditorRow PartialView which gives ddls with class03 and in main view where it got appended.(This is just to show you what is class03)

@model eKnittingData.InquiryOrderDetail
@using eKnitting.Helpers

@using (Html.BeginCollectionItem("InquiryOrderDetails"))
{
<div class="editorRow">
    @Html.DropDownListFor(a => a.ComponentId, (SelectList)ViewBag.CompList, "Select", new { Class = "class02" })
    @Html.DropDownListFor(a => a.DesignCodeId, (SelectList)ViewBag.DCodeList, "Select", new { Class = "class03" })
    @Html.TextBoxFor(a => a.NoOfParts, new { Class = "class01" })
    <a href="#" class="deleteRow">delete</a>
</div>
}

and in main view it got appended to 

<div id="editorRows">         
            @foreach (var item in Model.InquiryOrderDetails)
            {
                Html.RenderPartial("_DetailEditorRow", item);
            }           
    </div>

_ComponentDetails PartialView to render items(a list has been passed at once)

@model List<eKnittingData.InquiryComponentDetail>
@using eKnitting.Helpers

<div class="cmpCls">
@foreach(var icd in Model)
{
    using (Html.BeginCollectionItem("InquiryComponentDetails"))
    {
    <div class="innerCmpCls">
        @Html.DisplayFor(a => icd.DesignCodeId)
        @Html.DisplayFor(a => icd.QualityReferenceId)            
        @Html.TextBoxFor(a => icd.Height, new { Class="clsHeight clsSameHL"})
        @Html.TextBoxFor(a => icd.Length, new { Class = "clsLength clsSameHL" }) 
        @Html.TextBoxFor(a => icd.GscmComp, new { Class = "clsGscmComp clsSameHL" })
        @Html.TextBoxFor(A => icd.Wastage, new { Class = "clsWastage" })
        @Html.ActionLink("Fds", "View", new { id = icd.QualityReferenceId }, new { @class = "myLink", data_id = icd.QualityReferenceId })
        @Html.TextBoxFor(a => icd.TotalYarn, new { Class = "clsTotalYarn" })
        <br>
        <div class="popFds"></div>
    </div>
    }
}
</div> 

ActionResult that Passes a list at once and returns the PartialView

    public ActionResult ComponentDts(int DesignCdId)
    {
        var objContext = new KnittingdbContext();
        var QltyRefList = objContext.DesignCodeQltyRefs.Where(a=>a.DesignCodeId==DesignCdId).ToList();

        var iocdList = new List<InquiryComponentDetail>();

        foreach(DesignCodeQltyRef dcqr in QltyRefList)
        {
            iocdList.Add(new InquiryComponentDetail { 
                DesignCodeId=dcqr.DesignCodeId,
                QualityReferenceId=dcqr.QltyRefId
            });
        }
        return PartialView("_ComponentDetails", iocdList);
    }

ActionResult for GET

        var objContext = new KnittingdbContext();

        var newIovm = new InquiryOrderViewModel();
        var newIo = new InquiryOrder();
        var iocdL = new List<InquiryComponentDetail>();

        newIovm.InquiryOrder = newIo;
        newIovm.InquiryComponentDetails = iocdL;

        return View(newIovm);

ActionResult for POST

public ActionResult Save(InquiryOrderViewModel inquiryOrderViewModel)
{
    .........
}

When user selects an item from a dropdownlist(class03), the items related to that item are rendered to the view using the PartialView(_ComponentDetails') and get appended. Then user selects another item from another ddl(class03), the related items are rendered and appended after earlier appended ones. User can go on like this.

Rendering and appending items works fine. But for the PostBack even though i get the number of items in the list correctly(I checked it by putting a break point on POST ActionResult ) all items content show null values. Pls guide me in the correct way for achieving this. All help appreciated. Thanks!


Solution

  • Your _ComponentDetails view is generating form controls that have name attributes that look like (where ### is a Guid)

    name="InquiryComponentDetail[###].icd.Height"
    

    which does not match your model because typeof InquiryComponentDetail does not contain a property named icd. In order to bind to your model, your name attribute would need

    name="InquiryComponentDetail[###].Height"
    

    To generate the correct html, you will need 2 partials

    _ComponentDetailsList.cshtml (this will be called by the ComponentDts() method using return PartialView("_ComponentDetailsList", iocdList);)

    @model List<eKnittingData.InquiryComponentDetail>
    <div class="cmpCls">
      @foreach(var item in Model)
      {
        Html.RenderPartial("_ComponentDetails", item);
      }
    </div>
    

    _ComponentDetails.cshtml

    @model eKnittingData.InquiryComponentDetail
    using (Html.BeginCollectionItem("InquiryComponentDetails"))
    {
      <div class="innerCmpCls">
        @Html.DisplayFor(a => a.DesignCodeId)
        @Html.DisplayFor(a => a.QualityReferenceId)            
        @Html.TextBoxFor(a => a.Height, new { @class="clsHeight clsSameHL"}) // use @class, not Class
        @Html.TextBoxFor(a => a.Length, new { Class = "clsLength clsSameHL" }) 
        ....
      </div>
    }