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:
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.Go ahead and use the
EmailAddress.cshtml
editor template, but branch inside. AssumingMyCustomType
inherits fromString
:@model String
@if (Model is MyCustomType)
{
...
}
else
{
...
}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 defaultEmailAddress.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
How to Connect to an .Mdf (Microsoft SQL Server Database File) in a Simple Web Project
How to Use a Reserved Keyword as an Identifier in My JSON Model Class
How to Decode a Unicode Character in a String
How to Drag and Move Shapes in C#
Mail Sending with Network Credential as True in Windows Form Not Working
Nhibernate Queryover with Fetch Resulting Multiple SQL Queries and Db Hits
Unity How to Make a Visual Joystick in Unity
How to Determine If a Type Implements an Interface with C# Reflection
Exact Time Measurement for Performance Testing
Delegates: Predicate VS. Action VS. Func
How to Get the Groups of a User in Active Directory? (C#, ASP.NET)
Newtonsoft' Could Not Be Found
Properties VS. Fields: Need Help Grasping the Uses of Properties Over Fields