Passing a Collection to EditorFor in ASP.NET MVC
Asked Answered
S

1

9

I have a lengthy form which I have broken to several parts and am using @Html.EditorFor for each section which is working great but need your thoughts on whether this approach can be improved or not.

There are Segments and Every Segment can have Multiple Activities, So I have a Collection of Segments and every Segment in this collection contains a Collection of Activities.

    public class Activity
    {
        public string ActivityId { get; set; }
        public string ActivityDescription { get; set; }
        public bool IsSelected { get; set; }
    }
    public class Segment
    {
        public string SegmentId { get; set; }
        public string SegmentDescription { get; set; }
        public List<Activity> Activitites { get; set; }
    }

This was how I wanted the ViewModel that I use as a model for the view should look like but couldn't make it to work since @Html.EditorFor didn't accept a Collection Type.

    public class UserPreferencesViewModel
   {
       //..... Other Properties
       public List<Segment> Segments {get; set;}
   }

Here is the ViewModel

@model UserPreferencesViewModel
@{
   //... Other Properties
   @Html.EditorFor(m => m.Segments) //I assigned Segments three Segments in the Controller Get Method
}

Here is the EditorFor Template for Segments

@model List<Segment>
@{
   //... Other Properties
   @foreach(var segment in Model)
   {
      //Do the stuff
   }
}

But this doesn't work saying EditorFor cannot take collections and the exception is thrown at RunTime.

Here is my work Around. I created another Class "UglySegmentWorkAround" which contains the Segment Collection and then in the UserPreferencesViewModel I removed the List Property and instead defined a property for that.

public class UglySegmentWorkAround
{
public List<Segment> Segments {get; set;}
}

public class UserPreferencesViewModel
       {
           //..... Other Properties
           public UglySegmentWorkAround UglySegmentWorkAround {get; set;}
       }

and Here is the EditorFor Template.

@model UglySegmentWorkAround
    @{
       //... Other Properties
       @foreach(var segments in Model.Segments)
       {
          //Do the stuff
       }
    }

It works perfectly but I just don't feel comfortable with this approach, is there anything I am missing in the first approach? How this should be done? I don't want the EditorFor to do an implicit loop if I Pass it a collection because I am rendering a complex UI structure in the EditorFor and I need the EditorFor to have the loop inside it.

Sharisharia answered 26/9, 2014 at 21:34 Comment(2)
Check this linkGregarious
@HasanFahim I don't want to write a for loop outside the EditorFor as I have mentioned in my question, rather my UI requires me to loop on the collections inside the Editor for.Sharisharia
W
32

EditorFor is designed to iterate over collections for you. It does this automatically. When you pass a collection into an EditorFor, it will automatically call your template for each item in the collection.

If you need to setup some rendering for the collection as a whole then you should do this outside of the EditorFor call, either in your view code, or in a partial view which calls your EditorFor.

For instance, if you want to put your code in a table, you would do this (where MyCollection is List<MyItem>):

_MyCollectionPartial.cshtml

<table>
    <tr>
       <th>Foo...</th>
       ...
     <tr>
     @Html.EditorFor(x => x.MyCollection)
</table>

/Views/Shared/EditorTemplates/MyItem.cshtml

@model MyItem
<tr>
    <td>@Html.TextBox(x => x.Foo)</td>
    ....
</tr>

EDIT:

Perhaps a better way to do this is to use a little known and poorly documented "feature" of Editor templates. And that "feature" is that if you specify a template name as an argument, then it does not iterate over the collection. You can use this form to "wrap" your collection item templates.

/Home/Index.cshtml

.... your html
@Html.EditorFor(model => model.MyCollection, "MyCollectionLayout")

/Views/Shared/EditorTemplates/MyCollectionLayout.cshtml

@model List<MyItem>
<table>
    <tr>
       <th>Foo...</th>
       ...
     <tr>
     @Html.EditorForModel() (Or alternatively @Html.EditorFor(model => model)
 </table>

/Views/Shared/EditorTemplates/MyItem.cshtml

@model MyItem
<tr>
    <td>@Html.TextBoxFor(x => x.Foo)</td>
    ....
</tr>

NOTE: I say "feature" because this has generated many questions here on SO about it not iterating over collections when the template name is explicitly specified in the arguments)

Whencesoever answered 26/9, 2014 at 23:58 Comment(5)
"And that "feature" is that if you specify a template name as an argument, then it does not iterate over the collection" Much thanks for this.Shrug
It was necessary to put templates into the folder /Views/Shared/EditorTemplates befor I could get it to workJi
@LarsLadegaard - You're right, not sure how I mistyped that. I'll fix it in the post. I should note that EditorTemplates should only go in Shared if they are actually shared, they should be put in your Controllers Views folder if they are only needed in one controller.Whencesoever
Your answer is correct @ErikFunkenbusch. However, that "feature" also prevents itterating over a collection when you use the [UIHint] attribute. Where it could be obvious for most, it definitely was not for me...Penninite
@Penninite - I didn't mention anything about UIHint, but it basically works the same as specifying the template in the parameter, unfortunately. Any time you explicitly specify a template, then iteration over a collection is disabled, regardless of how you do it.Whencesoever

© 2022 - 2024 — McMap. All rights reserved.