Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing a Collection to EditorFor in ASP.NET MVC

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.

like image 955
Adil Khalil Avatar asked Sep 26 '14 21:09

Adil Khalil


1 Answers

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)

like image 74
Erik Funkenbusch Avatar answered Oct 30 '22 07:10

Erik Funkenbusch