4

I have a model which has an IEnumerable property (warning pseudo-code to follow)

public class PersonModel {
    public string Name { get; set; }
    public IEnumerable<AddressModel> Addresses { get; set; }
}

public class AddressModel {
    public string Name { get; set; }
}

I want to display the Address sub-objects in the same view

Person.cshtml

@model PersonModel

<form>
    <h2>Person</h2>

    @Html.EditorFor(m=>m.Name)

    <ul>@Html.EditorFor(m=>m.Addresses)</ul>

</form>

EditorTemplate/AddressModel

@model AddressModel

<li>@Html.TextboxFor(m=>m.Name)</li>

Marvellous, all works fine

But, being a git, I now want to start using Template Layouts to wrap all my properties in a standard type way and stuff

So I create a string.cshtml and a list.cshtml to do this, with a _Control.cshtml for the layout

EditorTemplates/_Control.cshtml

<div>
    @Html.Label(string.Empty)
    <div class="input">
        @RenderBody()                    

        @Html.ValidationMessage(string.Empty)
    </div>
</div>

EditorTemplates/string.cshtml

@model string
@{    
    Layout = "_Control.cshtml";
}
@Html.TextBox(string.Empty, Model)

(so far yay! but wait.. oh no..)

Here's the trouble

<ul>@Html.EditorFor(m=>m.Addresses)</ul>

from the main view (see above) becomes

@Html.EditorFor(m=>m.Addresses, "List")

EditorTemplates/list.cshtml

@model IEnumerable<object>
@{    
    Layout = "_Control.cshtml";
}
<ul>
    @foreach(var item in Model){
        @Html.EditorFor(m => item)
    }
</ul>

This renders the id and names incorrectly, something like Addresses_item_Name, which does not contain the Id, so adding the id with a for loop

@for (var i = 0; i < Model.Count();i++ ) {
    @Html.EditorFor(m => Model.ElementAt(i))
}

This blows up as the MVC expression helper does not allow anything but arrays, but Addresses has to be IEnumerable<> because EF4.1 does not support .ToArray inside a sub-query ie.

var model = (from p in DataContext.People
             where p.Id = 1
             select new PersonModel {
                 Name = p.Name,
                 Addresses = (from a in p.Addresses
                              select new AddressModel {
                                  Name = a.Name 
                              }).ToArray() // **NOT SUPPORTED**
             }).FirstOrDefault();

Has anyone come up against this? Is there are standard way to do it?

This works, but is it right?

EditorTemplates/list.cshtml

@model IEnumerable<object>
@{    
    Layout = "_Control.cshtml";
    var prefix = ViewData.TemplateInfo.HtmlFieldPrefix;
}
<ul>
    @for (var i = 0; i < Model.Count();i++ ) {
        var item = Model.ElementAt(i);
        ViewData.TemplateInfo.HtmlFieldPrefix = string.Format("{0}[{1}]",prefix, i);
        @Html.EditorFor(m => item, null, string.Empty)
    }
</ul>

The desired structure is

<form>
    <control>
       Person Name Control Elements
    <control>

    <control>
       Address List Control Elements
    <control>
</form>
Anthony Johnston
  • 9,405
  • 4
  • 46
  • 57
  • Why do you need this List.cshtml template? Can't see any added value in it, especially all that you do inside is write a loop and call another editor template for each element of the collection which is already automatically done by default. – Darin Dimitrov Apr 27 '11 at 16:41
  • 1
    Allows for the template layout – Anthony Johnston Apr 27 '11 at 17:01
  • @Anthony Johnston, but you already included the template layout in the `string.cshtml` template. Or you want a different layout? – Darin Dimitrov Apr 27 '11 at 17:03
  • The addresses as a whole are wrapped not each item, addressmodel.cshtml should be textboxfor, I'll fix that – Anthony Johnston Apr 27 '11 at 17:18
  • IMO you're better off doing a foreach and incrementing a counter variable separately. The Count() and ElementAt() methods are potentially expensive (depending on the underlying list) but simply enumerating (foreach) will be equally good regardless. – JT. May 10 '11 at 03:33
  • Ta JT - took your advice here – Anthony Johnston Jun 22 '11 at 13:31

1 Answers1

2

The EditorFor doesn't allow anything put property types, not methods, this will be why it is dying.

My advice would be to create the template as a single address item, then use a loop outside to edit out each one, or use editorformodel which does that for you.

Si

Slicksim
  • 7,054
  • 28
  • 32