MVC 4 Reversed Empty View Model

Ok, so there are many questions regarding this, but I can't seem to get this to work. It should be easy, but I remain 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 model (the virtual machine I'm really interested in)

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

      

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 takes the container's view model BookingIndexViewModel

and creates a form for each held BookingViewModel

. On view, I expect it to return the container view model with the changed BookingViewModel

, but will always be empty. I also tried expecting the only one BookingViewModel

that changed with the same result. Does MVC represent a hydrated view model of the same type as the type specified for the view or the type inferred from a form block?

The only severity values ​​I get from the view are modified attendanceId

(which needs to be populated by the dropdown helper) and the booking ID.

+3


source to share


5 answers


After looking at a similar question, I gave it a foreach(){}

for(){}

try instead for(){}

and it works now. I expect it to have something to do with the mutability of the elements in each type of loop.



0


source


The problem is almost certainly related to how you do it.

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

      

The HTML for the above would look something like this:

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

      

MVC model binding uses a field name

to bind data to the target model, in your scenario you have BookingIndexViewModel

, what you should find is at least filled with you in the field id

, but yours List<BookingViewModel>

will definitely be empty.



It is not clear what you are trying to do, I suspect you want to display all the booking information but only send a message BookingViewModel

back? If so, then you 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)
    }
}

      

+3


source


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

along with that you have to browse the html page to see how your html controls are named with the razor helpers as you see the model in your razor page

+1


source


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

I don't think it's a good idea to pass the entire list of objects from the view model back to the server. You have action Edit

. Change its parameter type to BookingViewModel

. This will allow you to edit an individual booking item. Then add something like @Html.HiddenFor(x => x.referralId)

to your form.

0


source


MVC Doesn't really represent or know anything about the expected ViewModel

expected action. It will not override yours ViewModel

as if it were a "viewstate".

It ultimately just lays out any input elements in your html on the form, and the Action method, when called, tries to extract (using binders) whatever you declared as a parameter from the values ​​included in your POST.

In your case, it seems that you will send the hidden Id and attendanceStatusId . If you look at the actual browser network traffic, you will notice that this is what is being published.

Hence, your action should expect a different ViewModel with two properties that match these input field names.

0


source







All Articles