Correct, Idiomatic Way to Use Custom Editor Templates with Ienumerable Models in ASP.NET MVC

Correct, idiomatic way to use custom editor templates with IEnumerable models in ASP.NET MVC

After discussion with Erik Funkenbusch, which led to looking into the MVC source code, it would appear there are two nicer (correct and idiomatic?) ways to do it.

Both involve providing correct html name prefix to the helper, and generate HTML identical to the output of the default EditorFor.

I'll just leave it here for now, will do more testing to make sure it works in deeply nested scenarios.

For the following examples, suppose you already have two templates for OrderLine class: OrderLine.cshtml and DifferentOrderLine.cshtml.


Method 1 - Using an intermediate template for IEnumerable<T>

Create a helper template, saving it under any name (e.g. "ManyDifferentOrderLines.cshtml"):

@model IEnumerable<OrderLine>

@{
int i = 0;

foreach (var line in Model)
{
@Html.EditorFor(m => line, "DifferentOrderLine", "[" + i++ + "]")
}
}

Then call it from the main Order template:

@model Order

@Html.EditorFor(m => m.Lines, "ManyDifferentOrderLines")

Method 2 - Without an intermediate template for IEnumerable<T>

In the main Order template:

@model Order

@{
int i = 0;

foreach (var line in Model.Lines)
{
@Html.EditorFor(m => line, "DifferentOrderLine", "Lines[" + i++ + "]")
}
}

ASP.NET MVC not finding/using custom type editor template

By default templates do not recurse down into nested complex objects. If you want this to happen you could always override this default behavior by creating a ~/Shared/EditorTemplates/Object.cshtml with the following contents:

@if (ViewData.TemplateInfo.TemplateDepth > 1) 
{
@ViewData.ModelMetadata.SimpleDisplayText
}
else
{
foreach (var prop in ViewData.ModelMetadata.Properties.Where(pm => pm.ShowForEdit && !ViewData.TemplateInfo.Visited(pm)))
{
if (prop.HideSurroundingHtml)
{
@Html.Editor(prop.PropertyName)
}
else
{
if (!String.IsNullOrEmpty(Html.Label(prop.PropertyName).ToHtmlString()))
{
<div class="editor-label">@Html.Label(prop.PropertyName)</div>
}
<div class="editor-field">
@Html.Editor(prop.PropertyName)
@Html.ValidationMessage(prop.PropertyName, "*")
</div>
}
}
}

You can read more about the default templates in ASP.NET MVC in this blog post.

EditorFor IEnumerable T with TemplateName


Is it any other, cleaner solution to do this?

The simple answer is no, it sucks badly, I completely agree with you, but that's how the designers of the framework decided to implement this feature.

So what I do is I stick to the conventions. Since I have specific view models for each views and partials it's not a big deal to have a corresponding editor template, named the same way as the type of the collection.

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

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
}

How can I use a custom editor template when using data annotations?

Data annotations always take precedence over the type when it comes to editor templates. You have a few options:

  1. Create a custom "EmailAddress" attribute. Technically, I think you could pretty much just subclass EmailAddressAttribute without actually adding anything additional. The name of your custom attribute, though, would allow you to have a different editor template for that.

  2. Go ahead and use the EmailAddress.cshtml editor template, but branch inside. Assuming MyCustomType inherits from String:

    @model String
    @if (Model is MyCustomType)
    {
    ...
    }
    else
    {
    ...
    }
  3. Probably the easiest method, though, is to simply specify the template. It's not quite as automatic, but it doesn't require any additional work:

     @Html.EditorFor(m => m.EmailAddress, "MyCustomType")

    Which would then load MyCustomType.cshtml instead of the default EmailAddress.cshtml.

Using ASP.NET MVC v2 EditorFor and DisplayFor with IEnumerable T Generic types

Use the attribute [UIHint("Tags")] then create a display template called Tags.ascx in the DisplayTemplates folder.

class MyModel 
{
[UIHint("Tags")]
IList<Tag> Tags { get; protected set; }
}

And in the file Tags.ascx

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<Tag>>" %>
<!-- put your Model code here ->

Works for me

ASP.Net MVC3 Model Binding IEnumerable T with Editor Template

Since you are only changing one at a time, I think the following is easier than trying to figure out at the controller which values changed, or adding a changed property and setting it via javascript.

Change Approve.cshtml to

@model IEnumerable<MvcWebsite.Models.Approve> 
<table>
<tr>
<th colspan=2>
Name
</th>
</tr>
@foreach(var user in Model){
@using (Html.BeginForm("Approve", "Registration", FormMethod.Post)) {
<tr>
<td>
@Html.EditorFor(m => user)
</td>
<td>
<input type="submit" value="Approve" class="submit-button" />
</td>
</tr>
}
}
</table>

Change the Approve Editor Template to

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


Related Topics



Leave a reply



Submit