2

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

Community
  • 1
  • 1
Donatas
  • 317
  • 1
  • 5
  • 18
  • Before debugging the DB save you'll need to successfully send the data to the controller action. What does the `SaveChanges()` POST request headers look like? – Jasen Apr 09 '14 at 18:21
  • Thank you, @Jasen. Just updated my post with more information. – Donatas Apr 09 '14 at 19:16
  • Your POST is for `CustomerOrder`. You're looking for the `SaveChanges` (which matches your action name) request instead. – Jasen Apr 09 '14 at 19:39
  • Sorry, @Jasen. Reposted correct headers now. – Donatas Apr 09 '14 at 20:24
  • You are missing (have not shared) part of the header. The header section **Form Data** is what's of interest to you. It will display the form fields sent and the parameter names (which will bind to your model). – Jasen Apr 09 '14 at 20:39
  • 1
    @Jasen. Finally! Used different approach of getting POST Headers :) `Form Dataview parsed MemberOrder.customerOrderLineID=8&MemberOrder.customerOrderQtyMax=78` – Donatas Apr 09 '14 at 21:08
  • Ok, now that we see the data is sent, are you seeing that at your server-side break point in `SaveChanges` just before `if(ModelState.IsValid)`? – Jasen Apr 09 '14 at 21:52
  • In Visual Studio I see `+ Form {MemberOrder.customerOrderLineID=8&MemberOrder.customerOrderQtyMax=44} System.Collections.Specialized.NameValueCollection {System.Web.HttpValueCollection}` while debugging before `if(ModelState.IsValid)`. this > base > Request > Form Is that correct? Or should I look somewhere else? @Jasen – Donatas Apr 09 '14 at 22:23
  • You might try adding `customerOrderLineID`, the row id, to the Bind Include list. – Jasen Apr 09 '14 at 23:25
  • Didn't helped. I also tried adding all attributes to the Bind Include list, but received same 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."}_ Form Data shows all values being submitted correctly, only they don't reach `SaveChanges()` method for some reason as `CustomerOrderline line` object holds empty values for each attribute. @Jasen – Donatas Apr 10 '14 at 01:06

1 Answers1

0

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);
}
Jasen
  • 14,030
  • 3
  • 51
  • 68
  • Thank you, @Jasen! You were right, the main problem was the Prefix. I added Solution bit to my post. Hopefully it will help others too. – Donatas Apr 10 '14 at 10:05