Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I make my list based editor template bind properly for a POST action?

I have a model, ApplicantBranchList, that is used as a property in a larger model as follows:

[Display(Name = "Where would you want to work?")]
public ApplicantBranchList PreferedBranches { get; set; }

ApplicantBranchList:

public class ApplicantBranchList : ViewModel
{
    public ApplicantBranchItem HeaderItem { get; set; }
    public ApplicantBranchList()
    {
        HeaderItem = new ApplicantBranchItem();
    }
    public void MapFromEntityList(IEnumerable<ApplicantBranch> applicantBranches)
    {
        var service = new BranchService(DbContext);
        var selectedIds = applicantBranches.Select(b => b.BranchId);
        Items = service.ReadBranches()
                       .Where(i => !i.IsDeleted)
                       .Select(p => new ApplicantBranchItem { BranchName = p.Name, WillWorkAt = selectedIds.Contains(p.Id) });
    }
    public IEnumerable<ApplicantBranchItem> Items { get; set; }
}

ApplicantBranchList has its own editor template, and an inner editor template for each item in ApplicantBranchList:

Views/Shared/EditorTemplates/ApplicantBranchList.cshtml:

@model Comair.RI.UI.Models.ApplicantBranchList
<table>
    <tr>
        <th style="display: none;"></th>
        <th>
            @Html.DisplayNameFor(model => model.HeaderItem.BranchName)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.HeaderItem.WillWorkAt)
        </th>
    </tr>
    @foreach (var item in Model.Items)
    {
        @Html.EditorFor(m => item)
    }
</table>

Views/Shared/EditorTemplates/ApplicantBranchItem.cshtml:

@model Comair.RI.UI.Models.ApplicantBranchItem
<tr>
    <td style="display: none;">
        @Html.HiddenFor(m => m.BranchId)
    </td>
    <td>
        @Html.DisplayFor(m => m.BranchName)
    </td>
    <td>
        @Html.EditorFor(m => m.WillWorkAt)
    </td>
</tr>

This editor renders properly in the view, but in the post action:

public ActionResult Create(ApplicantProfileModel model)
{
    if (ModelState.IsValid)
    {
        var branches = model.PreferedBranches;

PreferedBranches.Items is null.

What am I doing wrong?

like image 353
ProfK Avatar asked Jan 24 '13 09:01

ProfK


2 Answers

With MVC and editor templates you don't need to manually move through a list and call @HTMLEditorFor.

Doing this:

@Html.EditorFor(model => model.Items)

is the same as:

@for (var i = 0; i < Model.Items.Count; i++)
{
   @Html.EditorFor(model => model.Items[i]) // binding works only with items which are accessed by indexer
}

MVC will handle the iteration through your items and generate your editor template once per item. As is noted in the comments your template must be named the same as your model. Also, your model definition should be a singular representation of your model, not of type IEnumerable. Lastly, as noted in the comments, if you specify the template name parameter in your call to @Html.EditorFor() you will not have the benefit of the automatic iteration over your collection. You will need to manually iterate as is demonstrated above.

like image 76
kennythecoder Avatar answered Oct 22 '22 22:10

kennythecoder


The problem is that ASP.NET can't figure out how to bind to Model.Items property.

To to fix it replace:

public IEnumerable<ApplicantBranchItem> Items { get; set; }

with this:

public List<ApplicantBranchItem> Items { get; set; }

and instead of:

@foreach (var item in Model.Items)
{
   @Html.EditorFor(m => item)
}

use this one:

@for (var i = 0; i < Model.Items.Count; i++)
{
   @Html.EditorFor(model => model.Items[i]) // binding works only with items which are accessed by indexer
}
like image 45
SHSE Avatar answered Oct 22 '22 22:10

SHSE