0

I have a Code First project MVC 5. Simplified I have the following model:

public class Person
{
    public int Id { get; set; }
    public string ItemCode { get; set; }
    public string Description { get; set; }
}

In my view I have a table with textboxes:

@model IList<ItemApp.Models.Item>

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

@using(Html.BeginForm("Process", "Items", FormMethod.Post))
{
    @Html.AntiForgeryToken()

    <button type="button" class="btn btn-primary" id="btnAddRow">AddRow</button>
    <input type="submit" class="btn btn-success" value="process"/>
<table class="table">
    <tr>
        <th>
            ItemCode
        </th>
        <th>
            Description
        </th>
    </tr>

        @for (int i = 0; i < Model.Count(); i++ )
        {
            <tr>
                <td>
                    <input type="hidden" value="@i" name="Index" />

                    @Html.TextBoxFor(item => item[i].ItemCode)
                </td>
                <td>
                    @Html.TextBoxFor(item => item[i].Description)
                </td>

            </tr>
        }
    <tr>
        <td>
            <input type="hidden" value="4" name="Index" />
            <input type="hidden" name="[4].Id" />
            <input type="text" name="[4].ItemCode" />
        </td>
        <td>
            <input type="text" name="[4].Description" />
        </td>
    </tr>

</table>

}

As you can see in my view, I've added an extra row. The first 3 rows are populated via the database when running te project. When I enter values in the last row and submit the form, you can see that my action receives a list of Item objects (Count = 4). My process action looks like this:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Process(List<Item> items)
{
    if(ModelState.IsValid)
    {
        foreach(var item in items)
        {            
            if(item.Id != 0)
            {
                db.Entry(item).State = System.Data.Entity.EntityState.Modified;
            }
            else
            {
                db.Items.Add(item);
            }

            db.SaveChanges();
        }

        return RedirectToAction("Index");
    }

    return RedirectToAction("Index");
}

The problem is that my ModelState.IsValid returns false. This is because of the Id = 0 for the last (newly entered) row. What I want is to submit the form, update all items that are already in the database and add a new item.

kwv84
  • 943
  • 3
  • 11
  • 25
  • I assume the name of the class is actually `Item`, not `Person` based on your view and controller method –  Dec 10 '15 at 23:19

1 Answers1

2

Your ModelState is not invalid because Id=0 (zero is a valid value for int) Its invalid because you post back null for the 4th item. You need to change your view to

<input type="hidden" name="[4].Id" value="0"/>

so it posts back Id=0 (or you could simply omit the element and the value will be 0 when you submit the form, because that's the default value for int)

However, since you do not generate a form control for the Id property of each existing item, then those will also have Id=0 which means you will be adding the existing items again to the database, not updating them. Inside the loop you also need

@Html.HiddenFor(item => item[i].Id)

However you code is inflexible and will only ever work if you have exactly 3 existing items, and only ever allows you to add one new item. Suggest you review the answers here and here for dynamically (and deleting) items in a collection.

Community
  • 1
  • 1
  • I missed the `value="0"` part. In that way the debugger showed me 0 for item[4].Id, but it did not really have a value. I'm totally aware that the code is inflexible. That was going to be my next step. – kwv84 Dec 11 '15 at 05:21
  • Yes, it shows you `0` because the model binder tried to set it to `null` (but can't) so you just see the default value. When you debug, you need to check the values in the `ModelState` property (which will show you the properties that have errors) –  Dec 11 '15 at 05:26