Question

My Model:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace SNW.Models.CustomerOrderInfo
{
    public class MemberOrder : CustomerOrder
    {
        public new int customerOrderID { get; set; }
        [..]
        public int customerOrderLineID { get; set; }
        public int productID { get; set; }
        [..]
        public double customerOrderQtyMin { get; set; }
        public double customerOrderQtyMax { get; set; }
        [..]
    }
}

My Controller:

[..]
using SNW.Models;
using SNW.Models.CustomerOrderInfo;

namespace SNW.Controllers
{
    public class CustomerOrderController : Controller
    {
        private DBModelContainer db = new DBModelContainer();
        [HttpPost, ActionName("SaveChanges")]
        public ActionResult SaveChanges([Bind(Include = "customerOrderQtyMax")] SNW.Models.CustomerOrderInfo.MemberOrder line)
        {
            if(ModelState.IsValid)
            {
                    db.Entry(line).State = EntityState.Modified;
                    db.SaveChanges();
                    return RedirectToAction("CustomerOrder");
            }
            return View(line);
        }
    }
}

My View (CustomerOrder.cshtml):

@model IEnumerable<SNW.Models.CustomerOrderInfo.MemberOrder>
[..]
    @foreach (var MemberOrder in Model)
    {
    <tr>
        <td>@MemberOrder.productID</td>
        <td>@MemberOrder.productName</td>
        [..]
        <td>@Html.EditorFor(model => MemberOrder.customerOrderQtyMin)</td>
        <td>
            @using (Html.BeginForm("SaveChanges", "CustomerOrder", new { id = MemberOrder.customerOrderLineID }))
            {
                @Html.ValidationSummary(true)
                @Html.HiddenFor(model => MemberOrder.customerOrderLineID)
                @Html.EditorFor(model => MemberOrder.customerOrderQtyMax)
                @Html.ValidationMessageFor(model => MemberOrder.customerOrderQtyMax)
                <input type="submit" value="+" class="btn btn-default" />
            }
        </td>
    [..]
    </tr>

Rendered View:

The Problem:

Whenever I change Max value of Order Line and click "+" to update it in database, it doesn't work.

The line values in SaveChanges() method comes as either 0 or null, including customerOrderQtyMax value which is 0.0, but DB update doesn't happen either - former values are being shown again.

So my question is, how do I pass only customerOrderQtyMax input value to DB from this IEnumerable View? I want' to be able to amend order (or shopping cart if you wish) values right in CustomerOrder.cshtml page.

What have I tried so far:

  1. Updating Related Data with the Entity Framework in an ASP.NET MVC Application

  2. Various tutorials on Youtube

  3. Comparing SaveChanges() method to Edit() method generated from DB Model by EF6 for CustomerOrderLines entity. The later is not using IEnumerables<> and it has it's own View (Edit.cshtml).

  4. Added an extra HiddenFor() field in my View as my Order Lines' primary keys are composed of customerOrderLineID and customerOrderID attributes.

@using (Html.BeginForm("SaveChanges", "CustomerOrder", new { id = MemberOrder.customerOrderLineID })) { @Html.ValidationSummary(true) @Html.HiddenFor(model => MemberOrder.customerOrderLineID) @Html.HiddenFor(model => MemberOrder.customerOrderID) @Html.EditorFor(model => MemberOrder.customerOrderQtyMax) @Html.ValidationMessageFor(model => MemberOrder.customerOrderQtyMax) <input type="submit" value="+" class="btn btn-default" /> }

Thank you for your help, guys!

EDIT:

I've changed the code in SaveChanges() method quite a few times, but here is what errors I received:

  1. Reference to SNW.Models.CustomerOrderInfo.MemberOrder in public ActionResult SaveChanges([Bind(Include = "customerOrderQtyMax")] SNW.Models.CustomerOrderInfo.MemberOrder line) awards me with following error: {"The entity type MemberOrder is not part of the model for the current context."} probably because it's my custom made Model which inherits from CustomerOrder Model, because
    • CustomerOrder can be overwritten by EF Model generator
    • I need to use extra custom made properties for calculations
  2. Changed SNW.Models.CustomerOrderInfo.MemberOrder to original CustomerOrderLine and received following error: {"Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries."}.
    • CustomerOrderLine is generated by EF Model generator and I want its properties to merge with CustomerOrder to be able to edit Order's information in one window rather then going to Edit View for every single row (order line). That's why I made custom SNW.Models.CustomerOrderInfo.MemberOrder Model.
    • Now SaveChanges() is public ActionResult SaveChanges([Bind(Include = "customerOrderQtyMax")] CustomerOrderLine line)
  3. @Jasen. HTTP POST Header Thank you.
Request URL:http://localhost:64778/CustomerOrder/SaveChanges/8
Request Method:POST
Status Code:500 Internal Server Error
Request Headersview parsed
POST /CustomerOrder/SaveChanges/8 HTTP/1.1
Host: localhost:64778
Connection: keep-alive
Content-Length: 68
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: http://localhost:64778
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.154 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Referer: http://localhost:64778/CustomerOrder/CustomerOrder
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-GB,en;q=0.8,lt;q=0.6
Cookie: .ASPXFORMSAUTH=F44D36B23D1EA17ABB24DEEC4AFA7B09FD2D7B3EEB8D9C57D95561E9A9B334F4BA90F46AEAC2E2DE23958998B3F888342E507B92484C45D990CD0FD08D38F8D8D994CC8F5C231A0144DE4A7B89A286A1AFFEE765C86C856E71403FF94FDF873E
Form Dataview parsed
MemberOrder.customerOrderLineID=8&MemberOrder.customerOrderQtyMax=78
Response Headersview parsed
HTTP/1.1 500 Internal Server Error
Cache-Control: private
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcRG9uYXRhc1xTb3VyY2VcUmVwb3NcU291bmRzTmljZVdob2xlZm9vZHNcU05XXFNOV1xDdXN0b21lck9yZGVyXFNhdmVDaGFuZ2VzXDg=?=
X-Powered-By: ASP.NET
Date: Wed, 09 Apr 2014 21:03:23 GMT
Content-Length: 17180

HTML Form:

<form action="/CustomerOrder/SaveChanges/8" method="post"> <input data-val="true" data-val-number="The field customerOrderLineID must be a number." data-val-required="The customerOrderLineID field is required." id="MemberOrder_customerOrderLineID" name="MemberOrder.customerOrderLineID" type="hidden" value="8"> <input class="text-box single-line" data-val="true" data-val-number="The field customerOrderQtyMax must be a number." data-val-required="The customerOrderQtyMax field is required." id="MemberOrder_customerOrderQtyMax" name="MemberOrder.customerOrderQtyMax" type="text" value="3"> <span class="field-validation-valid" data-valmsg-for="MemberOrder.customerOrderQtyMax" data-valmsg-replace="true"></span>
<input type="submit" value="+" class="btn btn-default"> </form>

  1. Tried a solution from this StackOverflow answer and changed the code to [HttpPost, ActionName("SaveChanges")] public ActionResult SaveChanges([Bind(Include = "customerOrderQtyMax")] CustomerOrderLine line) // whitelist fields { CustomerOrderLine lineTemp = new CustomerOrderLine() { customerOrderQtyMax = line.customerOrderQtyMax }; if(ModelState.IsValid) { db.Entry(lineTemp).State = EntityState.Added; db.SaveChanges(); return RedirectToAction("CustomerOrder"); } return View(lineTemp); }

And now got this error: {"Cannot add or update a child row: a foreign key constraint fails (\"c1snw\".\"CustomerOrderLines\", CONSTRAINT \"FK_ProductCustomerOrderLine\" FOREIGN KEY (\"productID\") REFERENCES \"Products\" (\"productID\") ON DELETE NO ACTION ON UPDATE NO ACTION)"}, which probably means... well, I'm not sure if correct line is selected from DB using this code and whether it is trying to Update or Add a new line. Will check that.

SOLUTION:

@Jasen's suggestion of using Prefix="MemberOrder" in Bind helped to get code working!

However, I got rid of Include= statement in Bind, because it only worked when all line's attributes, that are not initially empty (null or 0) are included, and since I need all of them, I removed Include= in Controller and added more HiddenFor fields in the View. If I haven't had done this, Controller would attempt to change initial values to null) or 0.

Final code:

My Controller:

[HttpPost, ActionName("SaveChanges")]
public ActionResult SaveChanges([Bind(Prefix="MemberOrder")] CustomerOrderLine line) // whitelist fields
{
    if(ModelState.IsValid)
    {
            db.Entry(line).State = EntityState.Modified;
            db.SaveChanges();
            return RedirectToAction("CustomerOrder");
    }
    return View(line);
}

My View:

@using (Html.BeginForm("SaveChanges", "CustomerOrder", new { id = MemberOrder.customerOrderLineID }))
{
    @Html.ValidationSummary(true)
    @Html.HiddenFor(model => MemberOrder.customerOrderLineID)
    @Html.HiddenFor(model => MemberOrder.customerOrderID)
    @Html.HiddenFor(model => MemberOrder.productID)
    @Html.HiddenFor(model => MemberOrder.customerOrderQtyMin)
    @Html.EditorFor(model => MemberOrder.customerOrderQtyMax)
    @Html.HiddenFor(model => MemberOrder.customerOrderQtyActual)
    @Html.ValidationMessageFor(model => MemberOrder.customerOrderQtyMax)
    <input type="submit" value="+" class="btn btn-default" />
}

More resources on similar issues:

  1. ASP.NET MVC Model Binding - Part1 (CodeProject)

  2. How to use Bind Prefix? (StackOverflow)

  3. ASP.Net MVC3 Model Binding IEnumerable with Editor Template (StackOverflow)

  4. How to bind multiple prefixes? (ASP.NET)

  5. Multiple submit buttons with ASP.NET MVC: final solution

Was it helpful?

Solution

The key problem here is the for loop is messing up the posted variable names so that automatic binding will fail. So you'll need to add the Prefix parameter to your Bind.

[Bind(Prefix="MemberOrder", ...)]

The Prefix you specified in the EditorFor helper in your view:

@Html.EditorFor(model => MemberOrder.customerOrderQtyMax)

Also, add the row ID to the Bind Include.

[HttpPost, ActionName("SaveChanges")]
public ActionResult SaveChanges(
    [Bind(Prefix="MemberOrder", Include = "customerOrderLineID,customerOrderQtyMax")]
        SNW.Models.CustomerOrderInfo.MemberOrder line)
{
    if(ModelState.IsValid)
    {
        db.Entry(line).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("CustomerOrder");
    }
    return View(line);
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top