ASP.NET MVC Post list becomes null under very strange circumstances

asp.net-mvc, asp.net-mvc-4, http-post, list

Solution

You found a bug in the PrefixContainer of MVC 4 which has already been fixed in MVC 5.

Here is the fixed version with comments about the bug:

internal bool ContainsPrefix(string prefix)
{
    if (prefix == null)
    {
        throw new ArgumentNullException("prefix");
    }

    if (prefix.Length == 0)
    {
        return _sortedValues.Length > 0; // only match empty string when we have some value
    }

    PrefixComparer prefixComparer = new PrefixComparer(prefix);
    bool containsPrefix = Array.BinarySearch(_sortedValues, prefix, prefixComparer) > -1;
    if (!containsPrefix)
    {
        // If there's something in the search boundary that starts with the same name
        // as the collection prefix that we're trying to find, the binary search would actually fail.
        // For example, let's say we have foo.a, foo.bE and foo.b[0]. Calling Array.BinarySearch
        // will fail to find foo.b because it will land on foo.bE, then look at foo.a and finally
        // failing to find the prefix which is actually present in the container (foo.b[0]).
        // Here we're doing another pass looking specifically for collection prefix.
        containsPrefix = Array.BinarySearch(_sortedValues, prefix + "[", prefixComparer) > -1;
    }
    return containsPrefix;
}

Problem

So I have a controller like this: ``` public class TestController : Controller { // // GET: /Test/ public ActionResult Index() { return View("Test"); } public ActionResult Post(IList<Test> LanguageStrings, IList<Test> LanguageStringsGroup, IList<string> Deleted, IList<string> DeletedGroup) { if (LanguageStrings == null) { throw new ApplicationException("NULL"); } return View("Test"); } } public class Test { public string Val { get; set; } public string Another { get; set; } } ``` And a view like this: ``` <h2>Test</h2> @using (Html.BeginForm("Post", "Test")) { @Html.Hidden("LanguageStrings[0].Val", "test1") @Html.Hidden("LanguageStrings[0].Another") @Html.Hidden("LanguageStrings[1].Val", "test2") @Html.Hidden("LanguageStrings[1].Another") @Html.Hidden("LanguageStringsGroup[0].Val", "test4") @Html.Hidden("Deleted[0]") @Html.Hidden("Deleted[1]") @Html.Hidden("Deleted[2]") @Html.Hidden("DeletedGroup[0]") <button>Post</button> } ``` When I post the form my controller throws the exception because LanguageStrings is null. The strange part I mentioned in the title is that if I add one more record to the list everything works. Like this: ``` <h2>Test</h2> @using (Html.BeginForm("Post", "Test")) { @Html.Hidden("LanguageStrings[0].Val", "test1") @Html.Hidden("LanguageStrings[0].Another") @Html.Hidden("LanguageStrings[1].Val", "test2") @Html.Hidden("LanguageStrings[1].Another") @Html.Hidden("LanguageStrings[2].Val", "test3") @Html.Hidden("LanguageStrings[2].Another") @Html.Hidden("LanguageStringsGroup[0].Val", "test4") @Html.Hidden("Deleted[0]") @Html.Hidden("Deleted[1]") @Html.Hidden("Deleted[2]") @Html.Hidden("DeletedGroup[0]") <button>Post</button> } ``` It also works when I remove the "Deleted" list. Like this: ``` <h2>Test</h2> @using (Html.BeginForm("Post", "Test")) { @Html.Hidden("LanguageStrings[0].Val", "test1") @Html.Hidden("LanguageStrings[0].Another") @Html.Hidden("LanguageStrings[1].Val", "test2") @Html.Hidden("LanguageStrings[1].Another") @Html.Hidden("LanguageStringsGroup[0].Val", "test4") @Html.Hidden("DeletedGroup[0]") <button>Post</button> } ``` This has something to do with the naming I am using. I have already solved the problem with renaming LanguageStrings to something else. But I would like to understand what is happening here because probably I could learn something from it how MVC maps request body and will be able to avoid similar time consuming problems. Please help me and explain the cause of this.

Original source