MVC - Editing a list of objects
Asked Answered
P

2

12

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>
    }
}
Prismoid answered 2/10, 2012 at 19:31 Comment(6)
First: Nitpicking: In the title: list, not lit. Second: What is the httppost method you are talking about? Are we talking Android here? Please add that tag to denote it.Bernard
Did you read this: hanselman.com/blog/…Jolley
@KirillBestemyanov That's not quite what I'm looking for as I am expecting to get them back in the report model not as separate parameters to the functionPrismoid
@Bernard nitpicking for sure :P haha updated the title and elaborated more on what was meant by httppost (I assumed MVC in the title would imply the post back to the httppost method, I apologize for the miscommunication)Prismoid
You need to include your view code which shows how you render your list. Without that, we don't know what format it's being posted in.Mcandrew
@Kirill Bestemyanov Nice link thank youGerdi
G
19

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.

Grouch answered 3/10, 2012 at 12:39 Comment(4)
hmm I think that mine is an IEnumerable because I am using an entity object passed straight to the view and I believe it is expecting an IEnumerable. Basically I have to create a modelview object, this is kind of undesirable because there are about 180 fields in my object (no choice in reducing it cuz it's not my project lol).Prismoid
You have 180 fields on your form and all of them are required by the view? If this is the case then using a view model won't indeed bring much benefit. So in this case you could use a custom editor template. Don't write any for loops in the view. Simply replace the entire loop with @Html.EditorFor(x => x.items) and then then define a custom editor template for the SomeItem class - ~/Views/Shared/EditorTemplates/SomeItem.cshtml. ASP.NET MVC will automatically invoke this custom template for each element of the items collection, so the template will be strongly typed to @model SomeItemGrouch
Then inside the custom template put the markup with the divs and use the following expressions: @Html.TextBoxFor(x => x.propertyOne), .... In this case the helper will generate proper names for the input fields.Grouch
Yes I was looking into this as a solution, for the time being I have been moved onto a dif project and will have to return to this later while I juggle the two of them. Thanks for the advice I'm very new to MVC so using a view model is just more familiar and figured it would work. Glad that I know about this now tho cuz the 'EditorFor' seems like a really useful way of displaying things and would save time!Prismoid
M
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).

Mcandrew answered 2/10, 2012 at 20:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.