6

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="$('#item-@Model.AgentId').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="$('#item-@Model.AgentId').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

NealR
  • 10,189
  • 61
  • 159
  • 299
  • This is getting more and more complicated Neal :) First of all, you will need to share post action of controller, so we have any idea of what you "catching" in your action. Second, do you use fiddler? It would be very useful to you to see what is being posted to you controller. – Mariusz.W Apr 09 '13 at 19:07
  • @Mariusz.W: tell me about it.... Fiddler pic added per request. – NealR Apr 09 '13 at 22:46
  • The last item on this list from Fiddler contains 2 times FixedOrVariable field entry with identical key. There is no TableId field however. Not sure why that is the case since your partial view that you posted above looks good but just thought it was worth pointing out... – Marko Apr 11 '13 at 13:19
  • I have the same problem, anyone fixed this already? – Dylan Slabbinck Jul 08 '13 at 07:05
  • @DylanSlabbinck - I've been stuck on this for months, finally had to move on. If you find a solution, please post. Would be *greatly* appreciated. – NealR Jul 09 '13 at 14:41

2 Answers2

2

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="$('#item-@Model.AgentId').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!

Dylan Slabbinck
  • 846
  • 1
  • 16
  • 27
2

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()
});
actaram
  • 2,038
  • 4
  • 28
  • 49