Question

I have a simple strongly-typed view, and when I submit the form, one of the ViewModel properties is sometimes NULL for certain data; with most data it works fine. I have trimmed down my view to the simplest form that still reproduces the error:

@model eNPSWeb.Models.Survey_c.StartModel

@{
    Layout = null;
}

<html>
<head>
    @Html.DevExpress().GetScripts(new Script { ExtensionSuite = ExtensionSuite.Editors })
</head>
<body>
    @using (Html.BeginForm())
    {
        @Html.HiddenFor(model => model.EmployeeSurveyName)

        <input type="submit" />

        for (int x = 0; x < Model.Employees.Count(); ++x)
        {
        @Html.HiddenFor(model => model.Employees[x].Name)
        @Html.HiddenFor(model => model.Employees[x].ExternalId)
        @Html.HiddenFor(model => model.Employees[x].Included)
        }

        for (int x = 0; x < Model.Questions.Count(); ++x)
        {
        <div>
            <input type="hidden" name="@string.Format("{0}a", Model.Questions[x].ExternalId)"  />
            <input type="hidden" name="@string.Format("{0}b", Model.Questions[x].ExternalId)"  />
            @Html.HiddenFor(m => m.Questions[x].Id)
            @Html.HiddenFor(m => m.Questions[x].ExternalId)
            @Html.HiddenFor(m => m.Questions[x].Text)
            @Html.HiddenFor(m => m.Questions[x].Type)
            @Html.HiddenFor(m => m.Questions[x].AskWhy)
            @Html.HiddenFor(m => m.Questions[x].Mandatory)
            @Html.HiddenFor(m => m.Questions[x].Origin)
            @Html.HiddenFor(m => m.Questions[x].CommentText)
            @Html.HiddenFor(m => m.Questions[x].BeenUsed)
            @Html.HiddenFor(m => m.Questions[x].Included)
        </div>
        }
    }
</body>
</html>

This produces the following HTML:

<html>
<head>
    <script id="dxis_1397937255" src="/DXR.axd?r=1_142-_ND58" type="text/javascript"></script>
    <script id="dxis_29183559" src="/DXR.axd?r=1_80-_ND58" type="text/javascript"></script>
    <script id="dxis_1801595362" src="/DXR.axd?r=1_135-_ND58" type="text/javascript"></script>
    <script id="dxis_40984204" src="/DXR.axd?r=1_91-_ND58" type="text/javascript"></script>
    <script id="dxis_1510326740" src="/DXR.axd?r=14_0-VND58" type="text/javascript"></script>
    <script id="dxis_955216353" src="/DXR.axd?r=1_98-_ND58" type="text/javascript"></script>
    <script id="dxis_1818762533" src="/DXR.axd?r=1_104-_ND58" type="text/javascript"></script>
    <script id="dxis_1042080475" src="/DXR.axd?r=1_105-_ND58" type="text/javascript"></script>
    <script id="dxis_2035868379" src="/DXR.axd?r=1_101-_ND58" type="text/javascript"></script>
    <script id="dxis_237603203" src="/DXR.axd?r=1_84-_ND58" type="text/javascript"></script>
    <script id="dxis_964747995" src="/DXR.axd?r=1_109-_ND58" type="text/javascript"></script>
    <script id="dxis_787399442" src="/DXR.axd?r=14_23-VND58" type="text/javascript"></script>
    <script id="dxis_1504896761" src="/DXR.axd?r=1_92-_ND58" type="text/javascript"></script>
    <script id="dxis_1295258881" src="/DXR.axd?r=14_1-VND58" type="text/javascript"></script>
    <script id="dxis_1524734997" src="/DXR.axd?r=1_77-_ND58" type="text/javascript"></script>
    <script id="dxis_691625841" src="/DXR.axd?r=1_128-_ND58" type="text/javascript"></script>
    <script id="dxis_799056015" src="/DXR.axd?r=1_126-_ND58" type="text/javascript"></script>
    <script id="dxis_382503299" src="/DXR.axd?r=1_94-_ND58" type="text/javascript"></script>
    <script id="dxis_1728027670" src="/DXR.axd?r=1_97-_ND58" type="text/javascript"></script>
    <script id="dxis_879896160" src="/DXR.axd?r=1_95-_ND58" type="text/javascript"></script>
    <script id="dxis_1869190171" src="/DXR.axd?r=1_96-_ND58" type="text/javascript"></script>
    <script id="dxis_908190427" src="/DXR.axd?r=1_106-_ND58" type="text/javascript"></script>
    <script id="dxis_1718746384" src="/DXR.axd?r=14_4-VND58" type="text/javascript"></script>
    <script id="dxis_824974629" src="/DXR.axd?r=1_100-_ND58" type="text/javascript"></script>
    <script id="dxis_350755308" src="/DXR.axd?r=1_117-_ND58" type="text/javascript"></script>
    <script id="dxis_1538974427" src="/DXR.axd?r=1_103-_ND58" type="text/javascript"></script>
    <script id="dxis_395477619" src="/DXR.axd?r=14_12-VND58" type="text/javascript"></script>
    <script id="dxis_758481523" src="/DXR.axd?r=14_13-VND58" type="text/javascript"></script>
    <script id="dxis_1901978331" src="/DXR.axd?r=1_102-_ND58" type="text/javascript"></script>
    <script id="dxis_742498447" src="/DXR.axd?r=1_129-_ND58" type="text/javascript"></script>
    <script id="dxis_691084581" src="/DXR.axd?r=1_107-_ND58" type="text/javascript"></script>
</head>
<body>

    <form action="/Survey/Start/03926d2d-6bc4-4856-ba1d-9c751ea31529" method="post">
        <input data-val="true" data-val-length="The field EmployeeSurveyName must be a string with a maximum length of 250." data-val-length-max="250" data-val-required=" * Required" id="EmployeeSurveyName" name="EmployeeSurveyName" type="hidden" value="" />
        <input type="submit" />
        <input id="Employees_0__Name" name="Employees[0].Name" type="hidden" value="" />
        <input id="Employees_0__ExternalId" name="Employees[0].ExternalId" type="hidden" value="" />
        <input id="Employees_0__Included" name="Employees[0].Included" type="hidden" value="" />
        <input id="Employees_1__Name" name="Employees[1].Name" type="hidden" value="" />
        <input id="Employees_1__ExternalId" name="Employees[1].ExternalId" type="hidden" value="" />
        <input id="Employees_1__Included" name="Employees[1].Included" type="hidden" value="" />
        <div>
            <input type="hidden" name="E5C665C9-1EA5-4DD5-97C6-20A47813A61Fa" />
            <input type="hidden" name="E5C665C9-1EA5-4DD5-97C6-20A47813A61Fb" />
            <input data-val="true" data-val-number="The field Id must be a number." data-val-required="The Id field is required." id="Questions_0__Id" name="Questions[0].Id" type="hidden" value="0" />
            <input id="Questions_0__ExternalId" name="Questions[0].ExternalId" type="hidden" value="E5C665C9-1EA5-4DD5-97C6-20A47813A61F" />
            <input data-val="true" data-val-length="The field Text must be a string with a maximum length of 1000." data-val-length-max="1000" id="Questions_0__Text" name="Questions[0].Text" type="hidden" value="" />
            <input data-val="true" data-val-required="The Type field is required." id="Questions_0__Type" name="Questions[0].Type" type="hidden" value="ZeroToTen" />
            <input data-val="true" data-val-required="The AskWhy field is required." id="Questions_0__AskWhy" name="Questions[0].AskWhy" type="hidden" value="False" />
            <input data-val="true" data-val-required="The Mandatory field is required." id="Questions_0__Mandatory" name="Questions[0].Mandatory" type="hidden" value="False" />
            <input data-val="true" data-val-required="The Origin field is required." id="Questions_0__Origin" name="Questions[0].Origin" type="hidden" value="Template" />
            <input data-val="true" data-val-length="The field CommentText must be a string with a maximum length of 1000." data-val-length-max="1000" id="Questions_0__CommentText" name="Questions[0].CommentText" type="hidden" value="" />
            <input data-val="true" data-val-required="The BeenUsed field is required." id="Questions_0__BeenUsed" name="Questions[0].BeenUsed" type="hidden" value="False" />
            <input data-val="true" data-val-required="The Included field is required." id="Questions_0__Included" name="Questions[0].Included" type="hidden" value="False" />
        </div>
        <div>
            <input type="hidden" name="F34DF428-AAC7-48F0-8236-670FD197B189a" />
            <input type="hidden" name="F34DF428-AAC7-48F0-8236-670FD197B189b" />
            <input data-val="true" data-val-number="The field Id must be a number." data-val-required="The Id field is required." id="Questions_1__Id" name="Questions[1].Id" type="hidden" value="0" />
            <input id="Questions_1__ExternalId" name="Questions[1].ExternalId" type="hidden" value="F34DF428-AAC7-48F0-8236-670FD197B189" />
            <input data-val="true" data-val-length="The field Text must be a string with a maximum length of 1000." data-val-length-max="1000" id="Questions_1__Text" name="Questions[1].Text" type="hidden" value="" />
            <input data-val="true" data-val-required="The Type field is required." id="Questions_1__Type" name="Questions[1].Type" type="hidden" value="ZeroToTen" />
            <input data-val="true" data-val-required="The AskWhy field is required." id="Questions_1__AskWhy" name="Questions[1].AskWhy" type="hidden" value="False" />
            <input data-val="true" data-val-required="The Mandatory field is required." id="Questions_1__Mandatory" name="Questions[1].Mandatory" type="hidden" value="False" />
            <input data-val="true" data-val-required="The Origin field is required." id="Questions_1__Origin" name="Questions[1].Origin" type="hidden" value="Template" />
            <input data-val="true" data-val-length="The field CommentText must be a string with a maximum length of 1000." data-val-length-max="1000" id="Questions_1__CommentText" name="Questions[1].CommentText" type="hidden" value="" />
            <input data-val="true" data-val-required="The BeenUsed field is required." id="Questions_1__BeenUsed" name="Questions[1].BeenUsed" type="hidden" value="False" />
            <input data-val="true" data-val-required="The Included field is required." id="Questions_1__Included" name="Questions[1].Included" type="hidden" value="False" />
        </div>
        <div>
            <input type="hidden" name="ab75f0d9-2be9-4f04-baab-e3157651ac14a" />
            <input type="hidden" name="ab75f0d9-2be9-4f04-baab-e3157651ac14b" />
            <input data-val="true" data-val-number="The field Id must be a number." data-val-required="The Id field is required." id="Questions_2__Id" name="Questions[2].Id" type="hidden" value="0" />
            <input id="Questions_2__ExternalId" name="Questions[2].ExternalId" type="hidden" value="ab75f0d9-2be9-4f04-baab-e3157651ac14" />
            <input data-val="true" data-val-length="The field Text must be a string with a maximum length of 1000." data-val-length-max="1000" id="Questions_2__Text" name="Questions[2].Text" type="hidden" value="" />
            <input data-val="true" data-val-required="The Type field is required." id="Questions_2__Type" name="Questions[2].Type" type="hidden" value="ZeroToTen" />
            <input data-val="true" data-val-required="The AskWhy field is required." id="Questions_2__AskWhy" name="Questions[2].AskWhy" type="hidden" value="False" />
            <input data-val="true" data-val-required="The Mandatory field is required." id="Questions_2__Mandatory" name="Questions[2].Mandatory" type="hidden" value="False" />
            <input data-val="true" data-val-required="The Origin field is required." id="Questions_2__Origin" name="Questions[2].Origin" type="hidden" value="Template" />
            <input data-val="true" data-val-length="The field CommentText must be a string with a maximum length of 1000." data-val-length-max="1000" id="Questions_2__CommentText" name="Questions[2].CommentText" type="hidden" value="" />
            <input data-val="true" data-val-required="The BeenUsed field is required." id="Questions_2__BeenUsed" name="Questions[2].BeenUsed" type="hidden" value="False" />
            <input data-val="true" data-val-required="The Included field is required." id="Questions_2__Included" name="Questions[2].Included" type="hidden" value="False" />
        </div>
        <div>
            <input type="hidden" name="9f98d45b-e988-4578-a8ab-625edb2c4bd9a" />
            <input type="hidden" name="9f98d45b-e988-4578-a8ab-625edb2c4bd9b" />
            <input data-val="true" data-val-number="The field Id must be a number." data-val-required="The Id field is required." id="Questions_3__Id" name="Questions[3].Id" type="hidden" value="0" />
            <input id="Questions_3__ExternalId" name="Questions[3].ExternalId" type="hidden" value="9f98d45b-e988-4578-a8ab-625edb2c4bd9" />
            <input data-val="true" data-val-length="The field Text must be a string with a maximum length of 1000." data-val-length-max="1000" id="Questions_3__Text" name="Questions[3].Text" type="hidden" value="" />
            <input data-val="true" data-val-required="The Type field is required." id="Questions_3__Type" name="Questions[3].Type" type="hidden" value="ZeroToTen" />
            <input data-val="true" data-val-required="The AskWhy field is required." id="Questions_3__AskWhy" name="Questions[3].AskWhy" type="hidden" value="False" />
            <input data-val="true" data-val-required="The Mandatory field is required." id="Questions_3__Mandatory" name="Questions[3].Mandatory" type="hidden" value="False" />
            <input data-val="true" data-val-required="The Origin field is required." id="Questions_3__Origin" name="Questions[3].Origin" type="hidden" value="Template" />
            <input data-val="true" data-val-length="The field CommentText must be a string with a maximum length of 1000." data-val-length-max="1000" id="Questions_3__CommentText" name="Questions[3].CommentText" type="hidden" value="" />
            <input data-val="true" data-val-required="The BeenUsed field is required." id="Questions_3__BeenUsed" name="Questions[3].BeenUsed" type="hidden" value="False" />
            <input data-val="true" data-val-required="The Included field is required." id="Questions_3__Included" name="Questions[3].Included" type="hidden" value="False" />
        </div>
        <div>
            <input type="hidden" name="41321194-3EDB-4712-8548-0FFD7B732AC9a" />
            <input type="hidden" name="41321194-3EDB-4712-8548-0FFD7B732AC9b" />
            <input data-val="true" data-val-number="The field Id must be a number." data-val-required="The Id field is required." id="Questions_4__Id" name="Questions[4].Id" type="hidden" value="0" />
            <input id="Questions_4__ExternalId" name="Questions[4].ExternalId" type="hidden" value="41321194-3EDB-4712-8548-0FFD7B732AC9" />
            <input data-val="true" data-val-length="The field Text must be a string with a maximum length of 1000." data-val-length-max="1000" id="Questions_4__Text" name="Questions[4].Text" type="hidden" value="" />
            <input data-val="true" data-val-required="The Type field is required." id="Questions_4__Type" name="Questions[4].Type" type="hidden" value="ZeroToTen" />
            <input data-val="true" data-val-required="The AskWhy field is required." id="Questions_4__AskWhy" name="Questions[4].AskWhy" type="hidden" value="False" />
            <input data-val="true" data-val-required="The Mandatory field is required." id="Questions_4__Mandatory" name="Questions[4].Mandatory" type="hidden" value="False" />
            <input data-val="true" data-val-required="The Origin field is required." id="Questions_4__Origin" name="Questions[4].Origin" type="hidden" value="Template" />
            <input data-val="true" data-val-length="The field CommentText must be a string with a maximum length of 1000." data-val-length-max="1000" id="Questions_4__CommentText" name="Questions[4].CommentText" type="hidden" value="" />
            <input data-val="true" data-val-required="The BeenUsed field is required." id="Questions_4__BeenUsed" name="Questions[4].BeenUsed" type="hidden" value="False" />
            <input data-val="true" data-val-required="The Included field is required." id="Questions_4__Included" name="Questions[4].Included" type="hidden" value="False" />
        </div>
        <div>
            <input type="hidden" name="F2DD07DB-B107-493B-A9DD-523CCD6DDD4Fa" />
            <input type="hidden" name="F2DD07DB-B107-493B-A9DD-523CCD6DDD4Fb" />
            <input data-val="true" data-val-number="The field Id must be a number." data-val-required="The Id field is required." id="Questions_5__Id" name="Questions[5].Id" type="hidden" value="0" />
            <input id="Questions_5__ExternalId" name="Questions[5].ExternalId" type="hidden" value="F2DD07DB-B107-493B-A9DD-523CCD6DDD4F" />
            <input data-val="true" data-val-length="The field Text must be a string with a maximum length of 1000." data-val-length-max="1000" id="Questions_5__Text" name="Questions[5].Text" type="hidden" value="" />
            <input data-val="true" data-val-required="The Type field is required." id="Questions_5__Type" name="Questions[5].Type" type="hidden" value="ZeroToTen" />
            <input data-val="true" data-val-required="The AskWhy field is required." id="Questions_5__AskWhy" name="Questions[5].AskWhy" type="hidden" value="False" />
            <input data-val="true" data-val-required="The Mandatory field is required." id="Questions_5__Mandatory" name="Questions[5].Mandatory" type="hidden" value="False" />
            <input data-val="true" data-val-required="The Origin field is required." id="Questions_5__Origin" name="Questions[5].Origin" type="hidden" value="Template" />
            <input data-val="true" data-val-length="The field CommentText must be a string with a maximum length of 1000." data-val-length-max="1000" id="Questions_5__CommentText" name="Questions[5].CommentText" type="hidden" value="" />
            <input data-val="true" data-val-required="The BeenUsed field is required." id="Questions_5__BeenUsed" name="Questions[5].BeenUsed" type="hidden" value="False" />
            <input data-val="true" data-val-required="The Included field is required." id="Questions_5__Included" name="Questions[5].Included" type="hidden" value="False" />
        </div>
    </form>
</body>
</html>

When I submit this form, the Employees property is NULL, though it should be a list with 2 items. But where it gets really strange is that all sorts of slight changes to the HTML or underlying razor change this to working properly:

  • If I change the GUID used in Question_5 to a different GUID, it works.
  • If I remove any single one of the hidden fields, it works (though of course the property corresponding to the field I remove won't be binded).
  • If I remove question 2 or question 6, it works. But removing any other single question doesn't help! Removing any two or more questions always fixes it.
  • If I remove the DevExpress().GetScripts, it works.
  • If I change the name of the first hidden input in the from string.Format("{0}a", Model.Questions[x].ExternalId) to string.Format("a{0}", Model.Questions[x].ExternalId), it works.

Basically, it appears that the name of the 2 inputs that aren't created by Html.HiddenFor() may be an underlying cause, because changing their name to not start with the GUID seems to fix it. But how is it possible that a particular GUID would cause this?

I've tried stepping into the MVC model binding code; I couldn't find where it went different between the times when it works and the times when it doesn't. I also overrode BindProperty() and used a custom model binder so I could look at the BindingContext and PropertyDescriptors. Everything looked identical between the times where it works and the times where it doesn't. When it works, after calling BindProperty on "Employees", it then calls it on each property for each employee (Name, ExternalId, Included). Then it calls my setter for the Employees list in my view model. When it doesn't work, after it calls BindProperty on Employees, it just moves onto Questions.

Anyway, I've been trying all these various things for many hours now; I can't even begin to think of a possible explanation outside of maybe my randomly generated GUID happens to start with a particular sequence of characters, that when combined with other elements on the form, causes some sort of GUID collision with a hashed value for the form or something crazy like that...

Edit I have simplified my view further; just keeping the simplest inputs that cause the issue:

@model eNPSWeb.Models.Survey_c.StartModel

@{
    Layout = null;
}

<html>
<head>
    <title>What the...</title>
</head>
<body>
    @Html.DevExpress().GetScripts(new Script { ExtensionSuite = ExtensionSuite.Editors })

    @using (Html.BeginForm())
    {
        <input type="hidden" name="EmployeeSurveyName" />

        <input type="submit" />

        <input name="Employees[0].Name" />
        <input name="Employees[0].ExternalId" />
        <input name="Employees[0].Included" />

        <input name="Employees[1].Name" />
        <input name="Employees[1].ExternalId" />
        <input name="Employees[1].Included" />

        <input name="a" />
        <input name="b" />
        <input name="c" />
        <input name="d" />
        <input name="e" />
        <input name="f" />
        <input name="g" />
        <input name="h" />
        <input name="i" />
        <input name="2" />

        <div>
            <input type="hidden" name="Questions[0].Id" />
            <input type="hidden" name="Questions[0].ExternalId" />
            <input type="hidden" name="Questions[0].Text" />
            <input type="hidden" name="Questions[0].Type" />
            <input type="hidden" name="Questions[0].AskWhy" />
            <input type="hidden" name="Questions[0].Mandatory" />
            <input type="hidden" name="Questions[0].Origin" />
            <input type="hidden" name="Questions[0].CommentText" />
            <input type="hidden" name="Questions[0].BeenUsed" />
            <input type="hidden" name="Questions[0].Included" />
        </div>
        <div>
            <input type="hidden" name="Questions[1].Id" />
            <input type="hidden" name="Questions[1].ExternalId" />
            <input type="hidden" name="Questions[1].Text" />
            <input type="hidden" name="Questions[1].Type" />
            <input type="hidden" name="Questions[1].AskWhy" />
            <input type="hidden" name="Questions[1].Mandatory" />
            <input type="hidden" name="Questions[1].Origin" />
            <input type="hidden" name="Questions[1].CommentText" />
            <input type="hidden" name="Questions[1].BeenUsed" />
            <input type="hidden" name="Questions[1].Included" />
        </div>
        <div>
            <input type="hidden" name="Questions[2].Id" />
            <input type="hidden" name="Questions[2].ExternalId" />
            <input type="hidden" name="Questions[2].Text" />
            <input type="hidden" name="Questions[2].Type" />
            <input type="hidden" name="Questions[2].AskWhy" />
            <input type="hidden" name="Questions[2].Mandatory" />
            <input type="hidden" name="Questions[2].Origin" />
            <input type="hidden" name="Questions[2].CommentText" />
            <input type="hidden" name="Questions[2].BeenUsed" />
            <input type="hidden" name="Questions[2].Included" />
        </div>
        <div>
            <input type="hidden" name="Questions[3].Id" />
            <input type="hidden" name="Questions[3].ExternalId" />
            <input type="hidden" name="Questions[3].Text" />
            <input type="hidden" name="Questions[3].Type" />
            <input type="hidden" name="Questions[3].AskWhy" />
            <input type="hidden" name="Questions[3].Mandatory" />
            <input type="hidden" name="Questions[3].Origin" />
            <input type="hidden" name="Questions[3].CommentText" />
            <input type="hidden" name="Questions[3].BeenUsed" />
            <input type="hidden" name="Questions[3].Included" />
        </div>
        <div>
            <input type="hidden" name="Questions[4].Id" />
            <input type="hidden" name="Questions[4].ExternalId" />
            <input type="hidden" name="Questions[4].Text" />
            <input type="hidden" name="Questions[4].Type" />
            <input type="hidden" name="Questions[4].AskWhy" />
            <input type="hidden" name="Questions[4].Mandatory" />
            <input type="hidden" name="Questions[4].Origin" />
            <input type="hidden" name="Questions[4].CommentText" />
            <input type="hidden" name="Questions[4].BeenUsed" />
            <input type="hidden" name="Questions[4].Included" />
        </div>
    }

</body>
</html>

When I mess with the names of the inputs a, b, c, etc; I get very surprising results:

Works fine:

<input name="a" />
<input name="b" />
<input name="c" />
<input name="d" />
<input name="e" />
<input name="f" />
<input name="g" />
<input name="h" />
<input name="1" />
<input name="2" />

Doesn't work (Employees is null):

<input name="a" />
<input name="b" />
<input name="c" />
<input name="d" />
<input name="e" />
<input name="f" />
<input name="g" />
<input name="h" />
<input name="i" />
<input name="2" />

Works fine:

<input name="a" />
<input name="b" />
<input name="c" />
<input name="d" />
<input name="e" />
<input name="f" />
<input name="g" />
<input name="h" />
<input name="i" />
<input name="j" />

Doesn't work:

<input name="b" />
<input name="c" />
<input name="d" />
<input name="e" />
<input name="f" />
<input name="g" />
<input name="h" />
<input name="i" />
<input name="2" />
<input name="3" />

It's as if very specific combinations of input names causes it to break. (In the full page, the input names are coming from a GUID).

Was it helpful?

Solution

This appears to be a known bug in MVC: https://aspnetwebstack.codeplex.com/workitem/616, http://forums.asp.net/t/1856357.aspx. I wasn't able to find anything on this sooner because I had no way of knowing to look up the PrefixContainer, until I copied the DefaultModelBinder code into my own custom model binder and stepped through it. The seemingly random changes that fixed or broke the page must have just been changing the order that the binary search was going in, so that it usually found the correct element first.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top