Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EditorFor not rendering enumerable without @foreach

This is rather baffling. Imagine a Company entity with an .Employees enumerable property, for the sake of simplicity. I've seen elsewhere on SO that the trick to getting EditorTemplates to render enumerables sensibly is to use syntax like this:

In Index.cshtml:

@Html.EditorFor(company => company.Employees, "EmployeeEditorTemplate") 

EmployeeEditorTemplate.cshtml expects a single employee:

@model MyNamespace.Models.Employee
//...
@Html.EditorFor(x => x.FullName)
// ...etc.

Posts like this one: Viewmodels List Is Null in Action ...indicate that the modelbinder is smart enough to figure out the binding and index the enumerable on the fly. I'm getting different results (exactly what I'd expect if the modelbinder wasn't supposed to be so mageriffic):

The model item passed into the dictionary is of type 
System.Collections.Generic.List`1[MyNamespace.Models.Employee]', 
but this dictionary requires a model item of type 
'MyNamespace.Models.Employee'.

Enumerating it myself renders the collection just fine, but emits the same id for every row's controls (which is less than optimal, of course, but it tells me the EditorTemplate is functionally correct):

@foreach (var item in Model.Employees)
{
    @Html.EditorFor(x => item, "EmployeeEditorTemplate")
}

This construct also fails to post the company.Employees collection back to my other controller action.

Any ideas on what's required to achieve the following?

  • Render the child collection with unique Ids per instance in the list
  • Post the child collection back with the model

I don't care if I have to enumerate it myself, nor whether it's inside or outside the EditorTemplate; I've just seen it said that this is unnecessary and that MVC works better if you let it handle things. Thanks.

like image 754
Paul Smith Avatar asked Jan 12 '23 18:01

Paul Smith


2 Answers

Per @David-Tansey (from this answer - see for a much more detailed explanation):

(changed to suit this question):

When you use @Html.EditorFor(c => c.Employees) MVC convention chooses the default template for IEnumerable. This template is part of the MVC framework, and what it does is generate Html.EditorFor() for each item in the enumeration. That template then generates the appropriate editor template for each item in the list individually - in your case they're all instances of Employee, so, the Employee template is used for each item.


To sum up his explanation on when you use the named template, you are, in effect, passing the entire IEnumerable<Employee> to a template expecting a single Employee by using @Html.EditorFor(company => company.Employees, "EmployeeEditorTemplate").

note: I really only answered this because it had no answer and I stumbled here first. At least now someone doesn't close it and potentially miss an answer without having to go through the rigmarole of getting duplicate question votes, blah blah.

like image 172
Marc Avatar answered Jan 21 '23 00:01

Marc


You can read about it here: Hanselman's article

In short, you should write in your view (index.cshtml):

int i=0;
@foreach (var item in Model.Employees)
{
   @Html.TextBox(string.Format("Model[{0}].FullName", i), item.FullName)
   i++;
}
like image 21
Kirill Bestemyanov Avatar answered Jan 21 '23 00:01

Kirill Bestemyanov