Incorrect List Model Binding indices when using HTML Helpers

前端 未结 1 409
余生分开走
余生分开走 2020-11-29 11:36

this is a tricky one to explain, so I\'ll try bullet pointing.

Issue:

  1. Dynamic rows (collection) available to user on View (add/delete)
相关标签:
1条回答
  • 2020-11-29 12:15

    The reason for this behavior is that the HtmlHelper methods use the value from ModelState (if one exists) to set the value attribute rather that the actual model value. The reason for this behavior is explained in the answer to TextBoxFor displaying initial value, not the value updated from code.

    In your case, when you submit, the following values are added to ModelState

    Cars[1].Make: Land Rover 2
    Cars[2].Make: Audi 3
    Cars[3].Make: Honda 4
    

    Note that there is no value for Cars[0].Make because you deleted the first item in the view.

    When you return the view, the collection now contains

    Cars[0].Make: Land Rover 2
    Cars[1].Make: Audi 3
    Cars[2].Make: Honda 4
    

    So in the first iteration of the loop, the TextBoxFor() method checks ModelState for a match, does not find one, and generates value="Land Rover 2" (i.e. the model value) and your manual input also reads the model value and sets value="Land Rover 2"

    In the second iteration, the TextBoxFor() does find a match for Cars[1]Make in ModelState so it sets value="Land Rover 2" and manual inputs reads the model value and sets value="Audi 3".

    I'm assuming this question is just to explain the behavior (in reality, you would save the data and then redirect to the GET method to display the new list), but you can generate the correct output when you return the view by calling ModelState.Clear() which will clear all ModelState values so that the TextBoxFor() generates the value attribute based on the model value.

    Side note:You view contains a lot of bad practice, including polluting your markup with behavior (use Unobtrusive JavaScript), creating label element that do not behave as labels (clicking on them will not set focus to the associated control), unnecessary use of <br/> elements (use css to style your elements with margins etc) and unnecessary use of new { @id = "car-make-" + i }. The code in your loop can be

    @for (int i = 0; i < Model.Cars.Count; i++)
    {
        <div class="form-group row">
            <hr />
            @Html.LabelFor(m => m.Cars[i].Make, "Make (@i)")
            @Html.TextBoxFor(m => m.Cars[i].Make, new { @class = "form-control" })
            ....
            <input type="hidden" name="Cars.Index" value="@i" />
            <button type="button" class="btn btn-sm btn-danger delete">Delete Entry</button>
        </div>
    }
    
    $('.delete').click(function() {
        $(this).closest('.form-group').remove();
    }
    
    0 讨论(0)
提交回复
热议问题