Quango
Quango

Reputation: 13458

What is correct approach to editing multiple items in MVC 3?

I have an base class Product (properties: int ProductID, string Name, decimal Price) and some instance types based on product, e.g. HardDisk which have additional properties.

I have experimenting with edits in a single view that has a model based on a list of products:

@model List<Product>

This view has a single editor statement:

@Html.EditorForModel(Model)

I created an editor template for the Product class, which is a single Ajax form to allow a user to edit the name for each product:

@model Product
@{
    // set unique IDs for result divs
    var resultDiv = "result" + Model.ProductID.ToString();
}
@using (Ajax.BeginForm("Update", new AjaxOptions
{
    HttpMethod = "POST",
    LoadingElementId = "working",
    UpdateTargetId = @resultDiv,
    InsertionMode = InsertionMode.Replace
}
))
{
    <div style="padding: 4px; margin-bottom: 4px; border: 1px solid gray; background-color: #eee;">
        @Html.HiddenFor(m => m.ProductID)
        @Html.DisplayFor(m => m.Name)
        @Html.LabelFor(m => m.Name)
        @Html.EditorFor(m => m.Name)
        <input type="submit" value="Save" />
        <span id="@resultDiv">...</span>
    </div>

}

This works fine for the first row (which has ID [0].Name) will send the data with a single entry. However editing a second or third line results in null data on the post.

Upvotes: 1

Views: 1350

Answers (3)

Quango
Quango

Reputation: 13458

Figured out the cause and solution here - the "Model Binding to A List" link was useful to read.

The problem here is that the model binder assumes an index will start with zero and continue. The generated names for each item follow this pattern:

[0].ProductID, [0].Name
[1].ProductID, [1].Name
[2].ProductID, [2].Name

However, because I was using a single AJAX form to submit for each entry the model binder could only create a valid list from the index if I edited the first item ( [0] ).

If I edited the second item, the form post had just values for [1].ProductId and [1].Name - so it wasn't a zero-based index - so the binder failed to recreate the product data -- hence the null value.

My solution was to treat the POST as what it really is - a single edit on a single object. To do this I needed to change the "name" attribute on each control to just "ProductID" and "Name" and then modified the Update method that handles the POST to expect a single Product type.

This then worked perfectly.

Upvotes: 1

MikMark
MikMark

Reputation: 547

we need to provide an index for each item to bind complex objects.

<% for (int i = 0; i < Product.count; i++) { %>

 <%: Html.HiddenFor(m => m[i].ProductID) %>

  <%: Html.TextBoxFor(m => m[i].Name) %>

<%: Html.TextBoxFor(m => m[i].Price) %> 

<% } %>

If you are using templated helpers,use pattern like below in main viewpage

<% for (int i = 0; i < Product.count; i++) { %>

  <%: Html.EditorFor(m => m[i]) %>

<% } %>

In Product.ascx (partail page or template helper)

 <%@ Control Inherits="ViewUserControl<Product>" %>

 <%: Html.HiddenFor(m => m[i].ProductID) %>

      <%: Html.TextBoxFor(m => m[i].Name) %>

    <%: Html.TextBoxFor(m => m[i].Price) %>

Upvotes: 0

JIA
JIA

Reputation: 1495

you should have a look at the following article

Model Binding To A List

Upvotes: 2

Related Questions