0
votes

I have a viewmodel, with two properties, one is a POCO class, the other is a double, when the controller builds and send the viewmodel out all the values are present and working on the page. When I POST the viewmodel the double value is set, and the object is present but the POCO class inside is null, which was not when the object is sent.

In the examples below, why is ticket.flight null on the POSTed viewmodel object?

ViewModel:

public class PayViewModel
{
    public virtual Ticket ticket { get; set; }
    public double amountToPay = 0.0;
    [Display(Name = "Amount To Pay")]
    [DataType(DataType.Currency)]
    [DisplayFormat(DataFormatString = "{0:N2}", ApplyFormatInEditMode = true)]
    public double AmountToPay
    {
        get
        {
            return amountToPay;
        }
        set
        {
            amountToPay = value;
        }
    }
}

Controller methods:

// GET: Tickets/Pay/5
public ActionResult Pay(int? id)
{
    if (id == null)
    {
        return RedirectToAction("Index");
    }
    Ticket ticket = db.tickets.Find(id);
    if (ticket == null)
    {
        return RedirectToAction("Index");
    }

    PayViewModel viewModel = new PayViewModel();
    viewModel.ticket = ticket;
    return View(viewModel);
}

// POST: Tickets/Pay/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Pay([Bind(Include = "AmountToPay,ticket,ticket.flight")]PayViewModel viewModel)
{
    if (ModelState.IsValid)
    {
        viewModel.ticket.AmountPaid += viewModel.AmountToPay;
        if (viewModel.ticket.AmountPaid >= viewModel.ticket.flight.ticketPrice)
        {
            viewModel.ticket.status = TicketStatusType.CONFIRMED;
        }
        db.Entry(viewModel.ticket).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(viewModel.ticket);
}

View code - https://gist.github.com/JonFriesen/f511a91b8e9a90c1b23c

1
Could you also add the View code? - dotnetom
@dotnetom Added a link to the viewcode in a GitHub GIST (for some reason StackOverflow wouldn't let me post it). Thanks for the response - jonfriesen
Remove the [Bind(Include = "AmountToPay,ticket,ticket.flight")] bit. What happens when you do that? - Jason Evans

1 Answers

4
votes

During the form submit the browser only submits the fields of type input. This means that from your view only these statements create the fields of type input:

@Html.HiddenFor(model => model.ticket.id)

@Html.EditorFor(model => model.AmountToPay, new { htmlAttributes = new { @class = "form-control" } })

When the request arrives to an MVC application, the binding uses the arrived values ticket.id and AmountToPay to construct an object of type PayViewModel. Since it cannot find any property from ticket.flight amongst the submitted values, it assumes that it should not exist and therefore it is assigned a default value of null. This is the expected behavior of model binder.

If you want to have the initialized values, I suggest one of the following:

  • On POST initialize the object from the database code and use only the submitted value of AmountToPay to perform your processing.
  • In the view add the hidden variables for the missing fields, e.g. @Html.HiddenFor(model => model.ticket.flight.source.airportCode). In this case the variables would be submitted therefore the object would be fully populated.