1

I have two classes:

public class bill {

    public int billId { get;set;}

    public decimal billAmount { get;set;}

    ...................

    public virtual ICollection<lift> Lifts { get;set;}
}

public class lift {

    public int liftId { get;set;}

    public int billId { get;set;}

    ......
}

I am accessing the child payments related to existing bills by using the following code in my view:

                    @for (int i = 0; i < Model.Lifts.Where(x => x.archived != true).ToList().Count; i++)
                    {
                        <tr>
                            <td>@Html.EditorFor(model => model.Lifts.ToList()[i].liftId, new { htmlAttributes = new { @class = "form-control" } })</td>
                            @Html.ValidationMessageFor(model => model.Lifts.ToList()[i].liftId, "", new { @class = "text-danger" })

                            <td>@Html.EditorFor(model => model.Lifts.ToList()[i].liftDate, new { htmlAttributes = new { @class = "form-control" } })</td>
                            @Html.ValidationMessageFor(model => model.Lifts.ToList()[i].liftDate, "", new { @class = "text-danger" })

                            <td>@Html.EditorFor(model => model.Lifts.ToList()[i].liftAmount, new { htmlAttributes = new { @class = "form-control" } })</td>
                            @Html.ValidationMessageFor(model => model.Lifts.ToList()[i].liftAmount, "", new { @class = "text-danger" })

                            <td>@Html.EditorFor(model => model.Lifts.ToList()[i].archived, new { htmlAttributes = new { @class = "form-control" } })</td>
                            @Html.ValidationMessageFor(model => model.Lifts.ToList()[i].archived, "", new { @class = "text-danger" })

                            <td>@Html.EditorFor(model => model.Lifts.ToList()[i].archivedDate, new { htmlAttributes = new { @class = "form-control" } })</td>
                            @Html.ValidationMessageFor(model => model.Lifts.ToList()[i].archivedDate, "", new { @class = "text-danger" })

                        </tr>
                    }

This is all working as expected including the field validation on the payments if I remove required fields from the lift class but when I post my model to save the changed bill the Model.Lifts entity is always null

How can I bind the changed lift entities from the related bill to the post of my edit controller for bill, I have tried appending the property to the list of other properties but this has not worked. My controller is as follows:

[HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Edit([Bind(Include = "billId,.......,Lifts")] bill bill)
        {
            if (ModelState.IsValid)
            {
                db.Entry(bill).State = EntityState.Modified;
                db.SaveChanges();
                return RedirectToAction("Index");
            }
            return View(bill);
        }
Jay
  • 3,012
  • 14
  • 48
  • 99
  • 1
    You cannot use `.ToList()` within the view (look at the html your generating - it does not relate to your model). Use a view model with a property `List` and set it (including the filtering) in the GET method –  Aug 02 '16 at 21:59
  • The HTML works fine the lifts are all displayed as expected that is not my issue – Jay Aug 02 '16 at 22:04
  • 1
    It does not work fine - look at the name attributes of the html your generating - it order to bind it needs to be `name=Lifts[0].liftId` etc. Again, you cannot use `.ToList()` inside your `HmlHelper` –  Aug 02 '16 at 22:10
  • Sorry I meant to say that it is displaying fine on screen I can see all of the related data, the name is [0].liftId so I guess that is where my problem is now laying, I am not using a ViewModel at the moment just the bill model itself – Jay Aug 02 '16 at 22:13
  • Yes, I know :) Use a view model. –  Aug 02 '16 at 22:20
  • But on the Get part could I not just call bill.Lifts = db.Lifts.where(.....) and this would then be the same thing no? – Jay Aug 02 '16 at 22:21
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/118960/discussion-between-stephen-muecke-and-jay). –  Aug 02 '16 at 22:23

1 Answers1

2

Your use of .ToList() within your HtmlHelpermethods is generating name attributes such as name="[0].liftId", name="[0].liftDate" etc, but in order to bind to your model, they need to be name="Lifts[0].liftId", name="Lifts[0].liftDate" (i.e. prefixed with the property name). In adition, you validation will not work correctly.

Since you editing data, you should be using a view model which will also allow you to filter the data in the GET method.

public class LiftVM // add validation and display attributes as required
{
    public int ID { get; set; } // no need for a hidden input if you using the default routing
    public DateTime Date { get; set; }
    .... // note that  a billId property will not be required
}

public class BillVM // add validation and display attributes as required
{
    public int ID { get; set; }
    public decimal Amount { get; set; }
    ....
    public IList<liftVM> Lifts { get; set; }
}

and in the GET method, map your data model to an instance of the view model

bill data = .... // your query to get the data model
BillVM model = new BillVM
{
    ID = data.billId,
    Amount = data.billAmount,
    ....
    Lifts = data.Lifts.Where(x => !x.archived).Select(x => new LiftVM
    {
        ID = x.liftId,
        Date = x.liftDate,
        ....
    }).ToList()
};
return View(model);

Note, tools such as automapper can make mapping data models to view models easier.

Then in the view

@model BillVM
....
@for (int i = 0; i < Model.Lifts.Count; i++)
{
    <tr>
        <td>
            @Html.EditorFor(m => m.Lifts[i].ID, new { htmlAttributes = new { @class = "form-control" } })
            // Note this need to be inside a <td>
            @Html.ValidationMessageFor(m => model.Lifts[i].ID, new { @class = "text-danger" })
        </td>

and finally, the parameter in your POST method will be BillVM and you map the view model back to the data model if the view model is valid

Community
  • 1
  • 1
  • Thanks Stephen I will review your approach and will certainly look into automapper thanks again – Jay Aug 02 '16 at 23:10