MVC - Editing a list of objects

2019-01-19 14:16发布

问题:

I have the following class layout in MVC:

public class ReportModel 
{
    List<SomeItem> items;
    string value;
    string anotherValue;
}

now I create a strongly typed view in MVC of this type and make editable text fields to edit each value as well as use a foreach loop to populate text fields to edit the items in the list of someitem.

when I submit to the httppost method the singular values come back fine in the reportmodel object but the list does not get returned in the object. How should this be done?

When I say httppost I am referring to the method that MVC is posting back to

[HttpPost]
public ActionResult EditReport(ReportModel report)
{
    // Save the report in here after the update on the UI side
}

View code for posting the list of someitem

if (Model.items != null && Model.items.Count > 0)
{
    for (int i = 0; i < Model.items.Count; i++)
    {                
        <div class="editrow">
            <div class="edititem">
                <div class="editor-label">
                    @Html.LabelFor(m => m.items.ElementAt(i).propertyOne)
                </div>
                <div class="editor-field">
                    @Html.TextBoxFor(m => m.items.ElementAt(i).propertyOne)
                    @Html.ValidationMessageFor(m => m.items.ElementAt(i).propertyOne)
                </div>
            </div>
            <div class="edititem">
                <div class="editor-label">
                    @Html.LabelFor(m => m.items.ElementAt(i).propertyTwo)
                </div>
                <div class="editor-field">
                    @Html.TextBoxFor(m => m.items.ElementAt(i).propertyTwo)
                    @Html.ValidationMessageFor(m => m.items.ElementAt(i).propertyTwo)
                </div>
            </div>
            <div class="edititem">
                <div class="editor-label">
                    @Html.LabelFor(m => m.items.ElementAt(i).propertyThree)
                </div>
                <div class="editor-field">
                    @Html.TextBoxFor(m => m.items.ElementAt(i).propertyThree)
                    @Html.ValidationMessageFor(m => m.items.ElementAt(i).propertyThree)
                </div>
            </div>
        </div>
    }
}

回答1:

Don't use ElementAt(1) in your lambda expressions => this ruins your input field names. Please read the blog post that Kirill suggested you.

So you could use indexed access:

for (int i = 0; i < Model.items.Count; i++)
{                
    <div class="editrow">
        <div class="edititem">
            <div class="editor-label">
                @Html.LabelFor(m => m.items[i].propertyOne)
            </div>
            <div class="editor-field">
                @Html.TextBoxFor(m => m.items[i].propertyOne)
                @Html.ValidationMessageFor(m => m.items[i].propertyOne)
            </div>
        </div>
        <div class="edititem">
            <div class="editor-label">
                @Html.LabelFor(m => m.items[i].propertyTwo)
            </div>
            <div class="editor-field">
                @Html.TextBoxFor(m => m.items[i].propertyTwo)
                @Html.ValidationMessageFor(m => m.items[i].propertyTwo)
            </div>
        </div>
        <div class="edititem">
            <div class="editor-label">
                @Html.LabelFor(m => m.items[i].propertyThree)
            </div>
            <div class="editor-field">
                @Html.TextBoxFor(m => m.items[i].propertyThree)
                @Html.ValidationMessageFor(m => m.items[i].propertyThree)
            </div>
        </div>
    </div>
}

Of course in order to have indexer access to the collection this assumes that your items property is declared as either List<SomeItem> or SomeItem[]. If it is an IEnumerable<SomeItem> it won't work. So simply change the type of this property on your view model.



回答2:

Kirill's reference to Scott Hanselman's blog entry is correct, but you're reading it too narrowly. In the example shown, he passes the array to the action method, but it could just as easily be contained within the parent model as well. The same concept applies.

However, one thing to know is that the default model binder does not instantiate nested classes, so it will not create an instance of the List class, which means it will always be null. To fix this, you must instantiate the empty list class in the constructor.

This is only part of the problem, though as the data must be formatted in the correct way for the model binder to bind it. This is where Scott's blog post comes in, as it provides the format needed for the model binder to recognize the data as a list.

This is typically handled for you if you use an EditorTemplate and use Html.EditorFor(m => m.Items) and then have a SomeItem.cshtml EditorTemplate. This deals with the issues of collection item naming (so long as you also use strongly typed helpers in the template as well).