Search code examples
c#asp.net-mvc.net-coremodel-binding

Can a Form POST be too large for MVC Model Binder


This problem occurs in .net core 3.1 MVC website.

I am having trouble getting my POST to bind to my controller action (the parameter always comes through as null). The data is loaded from a database, and is a large recursive structure. If I delete a few hundred lines of the JSON (out of 2500 or so) in the database it will bind OK.

The GET displays perfectly.

Even when I change my Action method parameter from my ViewModel to IFormCollection, it still comes through as null. Is there some limit here that I wasn't aware of? If the size is the issue, is there a better approach for POSTing the data?

Parent View

<form id="frmAdditionalCodes" name="frmAdditionalCodes" method="post">
    <table class="table">
        <thead>
            <tr>
                <th>
                    @Html.DisplayNameFor(model => model.Code)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.FullName)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.Description)
                </th>
                <th></th>
            </tr>
        </thead>
        <tbody>

            <tr>

                <td style="width:50px">
                    @Html.DisplayFor(modelItem => Model.Code)
                    <input asp-for="@Model.Code" class="form-control" style="display:none" />
                </td>
                <td style="width:50px">
                    @Html.DisplayFor(modelItem => Model.FullName)
                    <input asp-for="@Model.FullName" class="form-control" style="display:none" />
                </td>
                <td style="width:50px">
                    @Html.DisplayFor(modelItem => Model.Description)
                    <input asp-for="@Model.Description" class="form-control" style="display:none" />
                </td>
                <td style="width:20px">
                    <span>
                        <i id="addTagItem" class="fas fa-folder-plus" title="Add child item"></i>
                    </span>

                </td>
                <td>
                    <partial name="~/Views/PartialViews/_SectionsAndTags.cshtml" model="@Model.Entities" view-data="ViewData" />
                </td>
                <td>
                </td>

            </tr>

        </tbody>
    </table>
</form>

Child View

<table class="partial_table">
    <thead>
        <tr>

            <th>
                @Html.DisplayNameFor(model => model.Code)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.FullName)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Description)
            </th>
            <th colspan="2">
                @Html.DisplayNameFor(model => model.Entities)
            </th>
        </tr>
    </thead>
    <tbody>
        @{ List<CdsDeclarationSectionAndTagItemViewModel> list = Model.ToList();
            for (int i = 0; i < list.Count(); i++)
            {
                    <tr>
                        @{
                            string elementNameCode = $"{list[i].Prefix}Code";
                            string elementNameFullName = $"{list[i].Prefix}FullName";
                            string elementNameDescription = $"{list[i].Prefix}Description";
                            string elementNameIsDeleted = $"{list[i].Prefix}IsDeleted";
                        }
                        <td>
                            <span>@list[i].Code</span>
                            <input asp-for="@list[i].Code" name="@elementNameCode" class="form-control" style="display: none" />
                        </td>
                        <td>
                            <span>@list[i].FullName</span>
                            <input asp-for="@list[i].FullName" class="form-control" name="@elementNameFullName" style="display: none" />
                        </td>
                        <td>
                            <span>@list[i].Description</span>
                            <input asp-for="@list[i].Description" class="form-control" name="@elementNameDescription" style="display: none" />
                        </td>
                        <td>
                            @if (list[i].Entities?.Length > 0)
                            {<img id="collapseItem" state="expanded" width="20" height="20" src="~/images/minus_PNG24.png" />
                            }

                            <span>
                                <i id="editTagItem" class="fas fa-pencil-alt" title="Edit item"></i>
                                <i id="deleteTagItem" class="far fa-trash-alt" title="Delete item"></i>
                                <i id="addTagItem" class="fas fa-folder-plus" title="Add child item"></i>
                                <i id="updateTagItem" class="far fa-save" title="Save changes" style="display: none"></i>
                                <i id="cancelTagItem" class="fas fa-undo-alt" title="Undo changes" style="display: none"></i>
                            </span>

                        </td>
                        <td>
                            @if (list[i].Entities?.Length > 0)
                            {
                                <partial name="~/Views/PartialViews/_SectionsAndTags.cshtml" model="@list[i].Entities" />
                            }
                        </td>
                        <td>
                            <input type="hidden" value="false" name="@elementNameIsDeleted" />

                        </td>

                    </tr>
            }
        }
    </tbody>
</table>

ViewModel

public class CdsDeclarationSectionAndTagViewModel
{
    public string Code { get; set; }

    public string FullName { get; set; }

    public string Description { get; set; }

    public CdsDeclarationSectionAndTagItemViewModel[] Entities { get; set; }
}

public class CdsDeclarationSectionAndTagItemViewModel
{
    public string Code { get; set; }
    public string FullName { get; set; }
    public string Description { get; set; }

    public CdsDeclarationSectionAndTagItemViewModel[] Entities { get; set; }

    public string Prefix { get; set; }

    public bool IsDeleted { get; set; }
}

Controller Here vm is null unless I remove some data

[HttpPost]
public async Task<IActionResult> CdsDeclarationSectionAndTag(CdsDeclarationSectionAndTagViewModel vm)
        {
        }

If I change it like so, fc is also null

[HttpPost]
public async Task<IActionResult> CdsDeclarationSectionAndTag(IFormCollection fc)
        {
        }

The posted Form Data looks like this (and can end up with 4 or 5 recursive levels)

Code: 42A FullName: Description: Declaration Entities[0].Code: 023 Entities[0].FullName: Acceptance (taxpoint) datetime Entities[0].Description: Acceptance (taxpoint) datetime Entities[0].IsDeleted: false Entities[1].Code: D026 Entities[1].FullName: LRN Entities[1].Description: LRN Entities[1].IsDeleted: false Entities[2].Code: D013 Entities[2].FullName: Declaration type Entities[2].Description: Declaration type Entities[2].IsDeleted: false Entities[3].Code: 109 Entities[3].FullName: Invoice total Entities[3].Description: Invoice total Entities[3].IsDeleted: false Entities[4].Code: 504 Entities[4].FullName: Specific circumstances indicator Entities[4].Description: Specific circumstances indicator Entities[4].IsDeleted: false Entities[5].Code: 131 Entities[5].FullName: Gross mass Entities[5].Description: Gross mass Entities[5].IsDeleted: false Entities[6].Code: 146 Entities[6].FullName: Total packages Entities[6].Description: Total packages Entities[6].IsDeleted: false Entities[7].Code: 61B Entities[7].FullName: Authentication Entities[7].Description: Authentication Entities[7].Entities[0].Code: 104 Entities[7].Entities[0].FullName: Signature/Authentication Entities[7].Entities[0].Description: Signature/Authentication Entities[7].Entities[0].IsDeleted: false Entities[7].IsDeleted: false Entities[8].Code: 02A Entities[8].FullName: Deferred Payment Entities[8].Description: Deferred Payment Entities[8].Entities[0].Code: D031 Entities[8].Entities[0].FullName: Deferment category code Entities[8].Entities[0].Description: Category code Entities[8].Entities[0].IsDeleted: false Entities[8].Entities[1].Code: D005 Entities[8].Entities[1].FullName: Deferment ID Entities[8].Entities[1].Description: ID Entities[8].Entities[1].IsDeleted: false Entities[8].Entities[2].Code: D006 Entities[8].Entities[2].FullName: Deferment Type Entities[8].Entities[2].Description: Type Entities[8].Entities[2].IsDeleted: false Entities[8].IsDeleted: false Entities[9].Code: 03A Entities[9].FullName: Additional Information Entities[9].Description: Additional Information Entities[9].Entities[0].Code: 226 Entities[9].Entities[0].FullName: Additional Information Statement code Entities[9].Entities[0].Description: Statement code Entities[9].Entities[0].IsDeleted: false ....


Solution

  • I found the answer on this thread. It turns out that by default, there is a limit of 1024 on Form Values that you can submit.

    I used the following code in Startup.cs to change the limit and the problem has gone away, and can now bind successfully

            services.Configure<FormOptions>(options =>
            {
                options.ValueCountLimit = int.MaxValue;
            });