3

Ok so there are lots of questions regarding this, but I can't seem to get this to work. It should be simple, but I'm left scratching my head.

Controller:

[HttpPost]
public ActionResult Edit(BookingIndexViewModel vm) // Also tried BookingViewModel
{
    return View();
}

Container view model:

public class BookingIndexViewModel
{
    public int id { get; set; }

    public List<BookingViewModel> bookings { get; set; }
    public List<SelectListItem> allAttendances { get; set; }
}

Booking view model (the VM I'm actually interested in)

public class BookingViewModel
{
    public int id { get; set; }
    public int referralId { get; set; }
    public int attendanceId { get; set; }
}

The View

@model Project.ViewModels.BookingIndexViewModel
@using Project.ViewModels

<fieldset>
    <legend>Registered patients</legend>
    @if (Model.bookings.Count < 1)
    {
        <div>None currently registered</div>
    }
    else
    {
    <table>
        <tr>
            <th>
                <span class="display-label">@Html.LabelFor(model => model.bookings[0].forenames)</span>
            </th>
            <th>
                <span class="display-label">@Html.LabelFor(model => model.bookings[0].surname)</span>
            </th>
            <th>
                <span class="display-label">@Html.LabelFor(model => model.bookings[0].dob)</span>
            </th>
            <th>
                <span class="display-label">@Html.LabelFor(model => model.bookings[0].attendance</span>
            </th>
        </tr>

        @{
            foreach (BookingViewModel b in Model.bookings)
            {
                using (Html.BeginForm("Edit", "Booking"))
                {
                    @Html.HiddenFor(x => x.id)
                    <tr>
                        <td>
                            <span class="display-field">@b.forenames</span>
                        </td>
                        <td>
                            <span class="display-field">@b.surname</span>
                        </td>
                        <td>
                            <span class="display-field">@String.Format("{0:dd/MM/yyyy}", b.dob)</span>
                        </td>
                        <td>
                            @Html.DropDownListFor(model => b.attendanceStatusId, Model.allAttendances)
                        </td>
                        <td>
                            <input type="submit" value="Save" />
                        </td>
                    </tr>
                }
            }
        }
    </table>
    }
</fieldset>

So the view accepts the container view model BookingIndexViewModel and creates a form for each BookingViewModel held. When submitted I expect it to pass back the container view model with the modified BookingViewModel but it's always empty. I've also tried expecting the single BookingViewModel that is modified with the same result. Does MVC submit a hydrated view model of the same type as the type given to the view or the type inferred from within the form block?

The only values of importance that I retrieve from the view is the modified attendanceId (that should be filled by the dropdown helper) and the booking id.

Lee
  • 3,869
  • 12
  • 45
  • 65

5 Answers5

4

The problem is almost certainly to do with how you are rendering this

foreach (BookingViewModel b in Model.bookings)
{
    @Html.HiddenFor(x => x.id);
    ...
    @Html.DropDownListFor(model => b.attendanceStatusId, Model.allAttendances)
}

The HTML for the above is going to look something like

<form ... >
<input type="hidden" name="BookingIndexViewModel.id" ... />
<select name="BookingViewModel.attendanceStatusId" ... />
</form>
...
<form ... >
<input type="hidden" name="BookingIndexViewModel.id" ... />
<select name="BookingViewModel.attendanceStatusId" ... />
</form>
...

The MVC model binder uses the name field to bind data to the destination model, in your scenario you have BookingIndexViewModel, what you should find is at a minimum your id field is populated, but your List<BookingViewModel> is definitely going to be empty.

It's not entirely clear what you are trying to do, I suspect that what you want is to display the entire booking information but only post an individual BookingViewModel back? If that's the case, the following should work

Controller

[HttpPost]
public ActionResult Edit(BookingViewModel model)
{
    ...
}

View

foreach (BookingViewModel b in Model.bookings)
{
    using (Html.BeginForm("Edit", "Booking"))
    {
        @Html.HiddenFor(b => b.id);
        @Html.HiddenFor(b => b.referralId);
        @Html.DropDownListFor(b => b.attendanceId, Model.allAttendances)
    }
}
James
  • 80,725
  • 18
  • 167
  • 237
1

In my opinion you should know how model binding work in asp.mvc ,here is good link that gives details about asp.mvc model binding http://msdn.microsoft.com/en-us/magazine/hh781022.aspx though article is very long it will help you out to know how model binding works

along with that you should view you html page to know how yours html controls are given names by razor helpers as per you view model in your razor page

0

There is no "postback" in ASP.NET MVC. It's stateless like HTTP do. If you want to submit something to server, you must include it in the form.

I don't think it's a good idea to pass the whole list of objects from view model back to server. You have an Edit action. Change it's parameter type to BookingViewModel. That will allow you to edit single booking item. Then add something like @Html.HiddenFor(x => x.referralId) in your form.

ilyabreev
  • 628
  • 6
  • 20
  • I understand that MVC is stateless, maybe I used the incorrect term but I have included everything I want in the form. Also, seeing as each `BookingViewModel` has its own form I am not sending the entire list back to the server - check the view. – Lee Aug 15 '14 at 10:46
  • Understood. When you submit a form, what field is missing? – ilyabreev Aug 15 '14 at 10:47
  • Everything was missing, it would not bind any properties from the submitted data - until I replaced the foreach with a usual for loop! – Lee Aug 15 '14 at 10:49
0

After looking at a similar question I gave using for(){} a try instead of foreach(){} and now it works. I expect it has something to do with the mutability of the elements in each type of loop.

Lee
  • 3,869
  • 12
  • 45
  • 65
0

MVC Doesn't actually submit anything in particular nor does it know about your expected ViewModel to be submitted to an Action. It will not repost your ViewModel as if it were "viewstate".

Ultimately it just posts whatever input elements are in your html within the form, and the Action method, when called, attempts to extract (using binders) whatever you declared as parameter from the values included in your POST.

In your case it seems you would only submit a hidden Id and the attendanceStatusId. If you look at the browser's actual network traffic you'll notice that is what is being posted.

Therefore, your Action should expect a different ViewModel with two properties that match those input field names.

Pablo Romeo
  • 11,298
  • 2
  • 30
  • 58