Question

I have a page that displays lists of payments to be invoiced, the payments are grouped into customers and there is a Ajax form around each group. When I submit the first form on the page for the first group of invoices it posts back correctly and I get the populate model sent to my controller to process. If I submit any of the other forms the form posts back with all the data(I can see it using firebug that its all getting sent) but MVC doesn't populate my model when calling the controller method, it just comes back as null.

My View:

@for (int i = 0; i < Model.InvoicingReportModels.Count(); i++)
{
    @Html.EditorFor(m => m.InvoicingReportModels[i], "_InvoicingReportModel")
}

_InvoiceReportMode.cshtml

@using (Ajax.BeginForm(new AjaxOptions() { HttpMethod = "POST", OnSuccess = "InvoiceSaved", OnFailure="InvoiceSaveFailed" }))
{
    @Html.HiddenFor(m => m.DisplaySummary)
    @Html.HiddenFor(m => m.MasterAccountID)
    <div class="settings_bordered_box" style="margin-bottom: 20px;">
        <div class="settings">
            <table>
                <tr>
                    <th>
                        @Html.DisplayFor(m => m.DisplaySummary)
                        <span class="settings_box_divider">| </span>
                        <span>@Html.Raw(Model.Payments.Count().ToString()) Invoices</span>
                        <span class="settings_box_divider">| </span>
                        @Html.DisplayFor(m => m.TotalPrice)
                    </th>
                </tr>
            </table>
        </div>
        <div class="settings">
            <table>
                <tr>
                    <th>
                        <input type="checkbox" id="@Html.IdForModel()_chkAll" class="selectAll" /></th>
                    <th>PaymentID</th>
                    <th>Date</th>
                    <th>Total Price</th>
                    <th>Payment Method</th>
                    <th>Invoice Number</th>
                    <th>&nbsp;</th>
                </tr>
                @for (int i = 0; i < Model.Payments.Count; i++)
                {
                    Model.Payments[i].InvoiceNumberFieldID = Html.IdFor(m => m.Payments[i].InvoiceNumber).ToString();
                    <tr>
                        <td style="text-align: center; vertical-align: middle;">@Html.CheckBoxFor(m => m.Payments[i].Selected) @Html.HiddenFor(m => m.Payments[i].SingleSaveSelected)</td>
                        <td style="text-align: center; vertical-align: middle;">@Html.DisplayFor(m => m.Payments[i].DisplayPaymentID) @Html.HiddenFor(m => m.Payments[i].PaymentID) @Html.HiddenFor(m => m.Payments[i].PaymentType)</td>
                        <td style="text-align: center; vertical-align: middle;">@Html.DisplayFor(m => m.Payments[i].Date)</td>
                        <td style="text-align: center; vertical-align: middle;">@Html.DisplayFor(m => m.Payments[i].TotalPrice)</td>
                        <td style="text-align: center; vertical-align: middle;">@Html.DisplayFor(m => m.Payments[i].PaymentMethod)</td>
                        <td style="text-align: center; vertical-align: middle;">@Html.EditorFor(m => m.Payments[i].InvoiceNumber) @Html.HiddenFor(m => m.Payments[i].InvoiceNumberFieldID)</td>
                        <td style="text-align: center; vertical-align: middle;">
                            <input type="submit" name="submit" class="coloured_button" value="Save" onclick="SetSingleSave('@Html.IdFor(m => m.Payments[i].SingleSaveSelected)'); return true;" />
                            &nbsp;&nbsp;
                        <input type="button" class="coloured_button" value="Details" />
                        </td>
                    </tr>
                }
                <tr>
                    <td style="text-align: center; vertical-align: middle;">&nbsp;</td>
                    <td style="text-align: center; vertical-align: middle;">&nbsp;</td>
                    <td style="text-align: center; vertical-align: middle;">&nbsp;</td>
                    <td style="text-align: center; vertical-align: middle;">&nbsp;</td>
                    <td style="text-align: center; vertical-align: middle;">&nbsp;</td>
                    <td style="text-align: center; vertical-align: middle;">@Html.EditorFor(m => m.InvoiceNumber)</td>
                    <td style="text-align: center; vertical-align: middle;">
                        <input type="submit" name="submit" class="coloured_button" value="Save Selected" />
                    </td>
                </tr>
            </table>
        </div>
    </div>
}

My Controller

[HttpPost]
public ActionResult Index(InvoicingModel model, string submit)
{
    ... Do controller stuff here
}

My Models

public class InvoicingModel
{
    public List<InvoicingReportModel> InvoicingReportModels { get; set; }
    public InvoicingModel()
    {
        InvoicingReportModels = new List<InvoicingReportModel>();
    }
}

public class InvoicingReportModel
    {
        public int MasterAccountID { get; set; }
        public string DisplaySummary { get; set; }
        public string InvoiceNumber { get; set; }
        [DataType(DataType.Currency)]
        public double TotalPrice
        {
            get
            {
                if (Payments != null && Payments.Count > 0)
                {
                    return Payments.Sum(p => p.TotalPrice);
                }
                else
                {
                    return 0.0;
                }
            }
        }
        public List<PaymentListModel> Payments { get; set; }

        public InvoicingReportModel()
        {
            Payments = new List<PaymentListModel>();
        }
    }

public class PaymentListModel
{
    public int PaymentID { get; set; }
    public string DisplayPaymentID { get; set; }
    [DataType(DataType.Currency)]
    public double TotalPrice { get; set; }
    public string PaymentMethod { get; set; }
    public string InvoiceNumber { get; set; }
    public bool Selected { get; set; }
    public bool SingleSaveSelected { get; set; }
    public DateTime Date { get; set; }

    public string InvoiceNumberFieldID { get; set; }
    public PaymentType PaymentType { get; set; }
}

The first form posts back the InvoicingModel populated with one InvoicingReportModel and the required number of PaymentListModels all fully populated. If it submit the 2nd or onwards forms the controller gets a InvoicingModel that has null for the InvoicingReportModel list.

I have tried changing the controller so that it accepts an InvoicingReportModel (because that is what the form is around) but then I just null for everything.

Not sure what else to try.

If it helps here is the firebug capture of the post data being sent from submitting the 3rd form on the page.

Parametersapplication/x-www-form-urlencoded
InvoicingReportModels[2]....    1316 - Some Thing Here
InvoicingReportModels[2]....    123
InvoicingReportModels[2]....    1316
InvoicingReportModels[2]....    
InvoicingReportModels[2]....    InvoicingReportModels_2__Payments_0__InvoiceNumber
InvoicingReportModels[2]....    13276
InvoicingReportModels[2]....    Standard
InvoicingReportModels[2]....    true
InvoicingReportModels[2]....    false
InvoicingReportModels[2]....    False
InvoicingReportModels[2]....    
InvoicingReportModels[2]....    InvoicingReportModels_2__Payments_1__InvoiceNumber
InvoicingReportModels[2]....    13298
InvoicingReportModels[2]....    Standard
InvoicingReportModels[2]....    true
InvoicingReportModels[2]....    false
InvoicingReportModels[2]....    False
X-Requested-With    XMLHttpRequest
submit  Save Selected
Source
submit=Save+Selected&InvoicingReportModels%5B2%5D.DisplaySummary=1316+-+Some+thing+Here&InvoicingReportModels%5B2%5D.MasterAccountID=1316&InvoicingReportModels%5B2%5D.Payments%5B0%5D.Selected=true&InvoicingReportModels%5B2%5D.Payments%5B0%5D.Selected=false&InvoicingReportModels%5B2%5D.Payments%5B0%5D.SingleSaveSelected=False&InvoicingReportModels%5B2%5D.Payments%5B0%5D.PaymentID=13276&InvoicingReportModels%5B2%5D.Payments%5B0%5D.PaymentType=Standard&InvoicingReportModels%5B2%5D.Payments%5B0%5D.InvoiceNumber=&InvoicingReportModels%5B2%5D.Payments%5B0%5D.InvoiceNumberFieldID=InvoicingReportModels_2__Payments_0__InvoiceNumber&InvoicingReportModels%5B2%5D.Payments%5B1%5D.Selected=true&InvoicingReportModels%5B2%5D.Payments%5B1%5D.Selected=false&InvoicingReportModels%5B2%5D.Payments%5B1%5D.SingleSaveSelected=False&InvoicingReportModels%5B2%5D.Payments%5B1%5D.PaymentID=13298&InvoicingReportModels%5B2%5D.Payments%5B1%5D.PaymentType=Standard&InvoicingReportModels%5B2%5D.Payments%5B1%5D.InvoiceNumber=&InvoicingReportModels%5B2%5D.Payments%5B1%5D.InvoiceNumberFieldID=InvoicingReportModels_2__Payments_1__InvoiceNumber&InvoicingReportModels%5B2%5D.InvoiceNumber=123&X-Requested-With=XMLHttpRequest
Was it helpful?

Solution

I worked out the solution. To use model binder with missing array elements you need to have a hidden field for each array element named Index. Moved the form into the toplevel view and added a hidden Index element for each iteration as below and all worked.

@for (int i = 0; i < Model.InvoicingReportModels.Count; i++ )
{   
    using (Ajax.BeginForm(new AjaxOptions() { HttpMethod = "POST", OnSuccess = "InvoiceSaved", OnFailure = "InvoiceSaveFailed" }))
    {
        @Html.Hidden("InvoicingReportModels.Index", i)
        @Html.EditorFor(m => Model.InvoicingReportModels[i], "_InvoicingReportModel")
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top