Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP MVC3 - BeginCollectionItem returning nulls

I am trying to use Steve Sanderson's blog post regarding binding collection items to a model. However, I'm seeing some weird behavior that I can't find an answer for in the post or other discussions.

In my model BankListMaster, I have an ICollection object of a separate model BankAgentId. BankListMaster and BankListAgentId have a one-to-many relationship in our SQL database.

I am running into the issue on the Edit page. When the page loads, the three agent Ids we currently have associated with the BankListMaster item I'm working with load properly. However, if I hit "Save", I see that the ICollection object (bankListAgentId) has a count of three items, but each respective field contains a null value.

If I select the Add another, then, following the instructions on the blog post, the Ajax calls a partial view which is appended to the table properly.

Now if I hit "Save", I see that the ICollection object count has increased by one item to a count of 4. All the items that were originally loaded with the GET again contain null field values, but the AgentId and StateCode fields for the newly appended item contain the correct information (all other fields for the new item are null, however).

Still relatively new to ASP MVC, so I'm not sure what is going on or what direction to look.

Here is the form from the main view. I've tried this with and without the @Html.Hidden items and received the same results.

@model Monet.Models.BankListMaster

@{
    ViewBag.Title = "Edit";
}
    <fieldset>
        <legend>Stat(s) Fixed</legend>
        <table id="fixedRows">
            <thead>
                <tr>
                    <th>State Code</th>
                    <th>Agent ID</th>
                    <th></th>
                    <th></th>
                </tr>
           </thead>
           <tbody>

                @for (int i = 0; i < Model.Fixed.Count; i++)
                {
                     using (Html.BeginCollectionItem("BankListAgentId"))
                    {                        
                             @Html.HiddenFor(m => Model.Fixed[i].BankID)
                             @Html.HiddenFor(m => Model.Fixed[i].TableId)
                             @Html.HiddenFor(m => Model.Fixed[i].FixedOrVariable)   
                            <tr>
                                <td>
                                    @Html.DropDownListFor(m => Model.Fixed[i].StateCode,
                                        (SelectList)ViewBag.StateCodeList, Model.Fixed[i].StateCode)
                                </td>
                                <td>
                                    @Html.TextBoxFor(m => Model.Fixed[i].AgentId)
                                    @Html.ValidationMessageFor(m => Model.Fixed[i].AgentId)
                                </td>
                                <td>
                                    <a href="javascript:void(0)" class="deleteRow">delete</a>
                                </td>
                                @*<td><a href="#" onclick="$('#[email protected]').parent().remove();" style="float:right;">Delete</a></td>*@
                            </tr>  

                    }
                }

           </tbody>

        </table>
        <br />
        <a href="javascript:void(0)" class="addFixed">Add Another</a>
    </fieldset>

Here is the partial view. Again, I've tried with and without the @Html.Hidden items and received the same result.

@model Monet.Models.BankListAgentId

@{
    Layout = null;
}
@using (Html.BeginCollectionItem("BankListAgentId"))
{ 
    @Html.HiddenFor(model => model.TableId)        
    @Html.HiddenFor(model => model.BankID)
    @Html.HiddenFor(model => model.FixedOrVariable)

    <tr>
        <td>
            @Html.DropDownListFor(model => model.StateCode,
                (SelectList)ViewBag.StateCodeList, Model.StateCode)
        </td>
        <td>
            @Html.EditorFor(model => model.AgentId)
            @Html.ValidationMessageFor(model => model.AgentId)
        </td>
        <td>
            <a href="javascript:void(0)" class="deleteRow">delete</a>

        </td>
        @*<td><a href="#" onclick="$('#[email protected]').parent().remove();" style="float:right;">Delete</a></td>*@
    </tr>
}

Here is the Ajax call

$(document).ready(function () {

    $(".addFixed").click(function () {
        $.ajax({
            url: '@Url.Action("BlankFixedRow", "BankListMaster")',
            dataType: 'html',
            cache: false,
            success: function (html) {
                $("#fixedRows > tbody").append('<tr>' + html + '</tr>');
            }
        });
    });
});

Here is the controller method that calls the partial view

    public ViewResult BlankFixedRow()
    {
        SelectList tmpList = new SelectList(new[] { "AL", "AK", "AS", "AZ", "AR", "CA", "CO", "CT", "DE", "DC", "FM", "FL", "GA", "GU", "HI", "ID", "IL", "IN", "IA", "KS", "KY", "LA", "ME", "MH", "MD", "MA", "MI", "MN", "MS", "MO", "MT", "NE", "NV", "NH", "NJ", "NA", "NM", "NY", "NC", "ND", "MP", "OH", "OK", "OR", "PW", "PA", "PR", "RI", "SC", "SD", "TN", "TX", "UT", "US", "VT", "VI", "VA", "WA", "WV", "WI", "WY" });
        ViewBag.StateCodeList = tmpList;

        return View("FixedPartialView", new BankListAgentId());
    }

This is the BankListMaster model

public partial class BankListMaster
{
    public BankListMaster()
    {
        this.BankListAttachments = new HashSet<BankListAttachments>();
        this.BankListAgentId = new HashSet<BankListAgentId>();
    }

    public int ID { get; set; }
    public string BankName { get; set; }
    public string LastChangeOperator { get; set; }
    public Nullable<System.DateTime> LastChangeDate { get; set; }

    public virtual ICollection<BankListAttachments> BankListAttachments { get; set; }
    public virtual ICollection<BankListAgentId> BankListAgentId { get; set; }
}

And this is the BankListAgentId model

public partial class BankListAgentId
{
    public string AgentId { get; set; }
    public int BankID { get; set; }
    public string FixedOrVariable { get; set; }
    public string StateCode { get; set; }
    public int TableId { get; set; }

    public virtual BankListMaster BankListMaster { get; set; }
}

Here is the what is sent from the form on the post back to the controller via fiddler. The items that are indexed 1, 2, and 3 are the items originally pulled from the database. The last item was added via the jQuery/Ajax call to the partial view.

enter image description here

like image 983
NealR Avatar asked Apr 09 '13 18:04

NealR


2 Answers

My solution was: everything used in the html.beginCollectionItem has to be in a partial view. So your solution could be something like this:

Main view

 @model Monet.Models.BankListMaster

 @{
     ViewBag.Title = "Edit";
 }
     <fieldset>
         <legend>Stat(s) Fixed</legend>
         <table id="fixedRows">
         <thead>
              <tr>
                   <th>State Code</th>
                   <th>Agent ID</th>
                   <th></th>
                   <th></th>
             </tr>
        </thead>
        <tbody>

            @for (int i = 0; i < Model.Fixed.Count; i++)
            {
                 @Html.Partial("name", item)     
            }

       </tbody>

    </table>
    <br />
    <a href="javascript:void(0)" class="addFixed">Add Another</a>
</fieldset>

Partial view "name"

 @model Monet.Models.BankListMaster

 using (Html.BeginCollectionItem("BankListAgentId"))
 {                        
      @Html.HiddenFor(m => Model.Fixed[i].BankID)
      @Html.HiddenFor(m => Model.Fixed[i].TableId)
      @Html.HiddenFor(m => Model.Fixed[i].FixedOrVariable)   
      <tr>
           <td>
                @Html.DropDownListFor(m => Model.Fixed[i].StateCode,
                  (SelectList)ViewBag.StateCodeList, Model.Fixed[i].StateCode)
           </td>
           <td>
                @Html.TextBoxFor(m => Model.Fixed[i].AgentId)
                @Html.ValidationMessageFor(m => Model.Fixed[i].AgentId)
           </td>
           <td>
                <a href="javascript:void(0)" class="deleteRow">delete</a>
           </td>
           @*<td><a href="#" onclick="$('#[email protected]').parent().remove();" style="float:right;">Delete</a></td>*@
           </tr>  

 }

It's not the complete solution, but you have to do it like this, it worked for me. Hope this helps out!

like image 111
Dylan Slabbinck Avatar answered Nov 11 '22 23:11

Dylan Slabbinck


I got stuck on a similar issue for several hours. My items collection (Choices) would return null when it'd contain more than one item. However, just like you, my data seemed to be rendered just fine:

{
    "QuestionTemplateId":"1",
    "Position":"0",
    "CardId":"1",
    "Label":"Question#1",
    "AdminComments":"",
    "Type":"ComboBox",
    "ModelType":"MyProject.Areas.DGM.Models.ViewModels.Controls.ComboBoxViewModel",
    "ComboQuestionId":"1",
    "Choices.Index": ["dc0e6eea-5a8e-4971-8f9f-4d6e1c290300","52f2b780-c21e-4633-b880-bdff5d815eaf"],
    "Choices[dc0e6eea-5a8e-4971-8f9f-4d6e1c290300].Label":"Choice #1",
    "Choices[dc0e6eea-5a8e-4971-8f9f-4d6e1c290300].Score":"4",
    "Choices[52f2b780-c21e-4633-b880-bdff5d815eaf].Label":"Choice #2",
    "Choices[52f2b780-c21e-4633-b880-bdff5d815eaf].Score":"7"
}

I then realized that my issue may be connected to the data POST. When the user submits the form, I'd make the following AJAX call:

$("#questionForm").on('submit', function () {
    if ($(this).valid()) {
        var data = $(this).serializeObject(); // from here: http://stackoverflow.com/a/1186309/2835243
        $.ajax({
            type: 'POST',
            url: this.action,
            contentType: 'application/json',
            data: JSON.stringify(data) // this is what generated the above JSON
        });
    }
}

What seemed to fix it for me is to send data as x-www-form-urlencoded (the default contentType for jQuery's AJAX) instead of json. So I changed my AJAX call for the following:

$.ajax({
    type: 'POST',
    url: this.action,
    data: $(this).serialize()
});
like image 20
actaram Avatar answered Nov 11 '22 23:11

actaram