ASP.NET MVC editor template for property
Asked Answered
E

2

13

Usually I render my forms by @Html.RenderModel, but this time I have a complex rendering logic and I render it manually. I decided to create a editor template for one property. Here is the code (copy pasted from default object editor template implementation):

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<% var modelMetadata = ViewData.ModelMetadata; %>
<% if (modelMetadata.HideSurroundingHtml) { %>
    <%= Html.Editor(modelMetadata.PropertyName) %>
<% } else { %>
    <% if (!String.IsNullOrEmpty(Html.Label(modelMetadata.PropertyName).ToHtmlString())) { %>
        <div class="editor-label"><%= Html.Label(modelMetadata.PropertyName) %></div>
    <% } %>
    <div class="editor-field">
        <%= Html.Editor(modelMetadata.PropertyName) %>
        <%= Html.ValidationMessage(modelMetadata.PropertyName) %>
    </div>
<% } %>

And here is how I use it:

@Html.EditorFor(x => x.SomeProperty, "Property") //"Property" is template above

But it didn't work: labels are rendered regardless of DisplayName and editors are not rendered at all (in Watches Html.Editor(modelMetadata.PropertyName shows empty string). What am I doing wrong?

Emilioemily answered 30/8, 2012 at 7:48 Comment(0)
R
12

You are calling editor from your editor. As @RPM1984 rephrases @darin-dmitrov in comment in this answer: You can only have 1 template used at runtime for a given type, in a given Views particular context.

If you change your view to render textbox instead of editor, it works, I just tried:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<% var modelMetadata = ViewData.ModelMetadata; %>
<% if (modelMetadata.HideSurroundingHtml)
   { %>
   <%= Html.Editor(modelMetadata.PropertyName) %>
<% }
   else
   { %>
   <% if (!String.IsNullOrEmpty(modelMetadata.DisplayName))
       { %>
       <div class="editor-label"><%= Html.Label(modelMetadata.PropertyName) %></div>
    <% } %>
    <div class="editor-field"><%= Html.TextBox(modelMetadata.PropertyName) %> <%= Html.ValidationMessage(modelMetadata.PropertyName) %></div>
<% } %>

If you want to render something else instead of textbox (i.e. dropdown list), you need to decide that inside your template for that property and render it. Or, if you have something common for more editors, I usually extract that into partial view in Shared folder, and just use Html.Partial("ViewName")

And, regarding labels are rendered regardless of DisplayName, to prevent label from rendering if there is no display name, change your if condition to !String.IsNullOrEmpty(modelMetadata.DisplayName) (I already put it that way in main code block)

EDIT This edit refers to question related to object.ascx default editor template. This is code of object.ascx, taken from Brad Wilson's blog:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<% 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>
        <% } %>
    <% } %>
<% } %>

This code indeed calls Html.Editor from inside of editor, but inside a loop which creates list of editors for properties of complex model. Each of these calls will invoke corresponding editor (i.e for string it will show string.ascx there etc), and only if you have some "unknown" property which is not string and there is no specific editor for it (i.e. byte[]) it will invoke object.ascx for it, but this is NOT calling editor for the current property (what you are trying to do):

The Object template’s primary responsibility is displaying all the properties of a complex object, along with labels for each property. However, it’s also responsible for showing the value of the model’s NullDisplayText if it’s null, and it’s also responsible for ensuring that you only show one level of properties (also known as a “shallow dive” of an object). In the next blog post, we’ll talk about ways to customize this template, including performing “deep dive” operations.


SUMMARY

Short version:

More editors for same property is basically solution for functional differences ("for yes/no I want here radio group and there drop-down)", and for visual differences partial views should be used, as you can nest them as much as you want, because you explicitly call them by name so there is no limits imposed, you are responsible to prevent any potential recursions.

Long version:

I have been investigating this, as I have the same problem, I'm using editor template to render <li> or <td> element (depending on config/theme) and from inside it call another editor which contains label and input (same for both cases, but if property is bool then input is before label), where I'm again calling third template for input (to prevent duplicating code for label/input and input/label scenarios), but this does not work. Although I did not found explanation on msdn or other relevant source, I figured out that the only scenario when editor gives nothing is when you want to render editor for the property which is the context of current editor (so it is actually exactly what I already quoted: "You can only have 1 template used at runtime for a given type, in a given Views particular context."). After thinking some more about this, I believe now that they are right in imposing this limit, as property x can only be rendered using one editor. You can have as many editors for property x as you want, but you cannot render one property once using more than one template. Any of your templates for rendering property x can use other templates to render PARTS of property x, but you cannot use (same or different) editor for x more than once (the same logic applies as to having two or more properties x (same type and name) on same model).

Besides, if you could insert another template for current property into current template, that enables chaining any number of templates for current property, and can easily cause recursion, so one way or another it will lead you to stackoverflow :)

Ripply answered 1/9, 2012 at 21:43 Comment(5)
Yes, I am calling editor from an editor but it is the same what happens when you use RenderModel with default Object editor template. And it works for Object editor template. So how does my code differ from it?Emilioemily
are you referring to default templates from bradwilson.typepad.com/blog/2009/10/…? There is a difference, prop.PropertyName is used to call editor for properties of complex models, for model itself <%= ViewData.ModelMetadata.SimpleDisplayText%> is used. Remember that for all properties their default editor will be called, so string for string property etc, so if there is no "specific" editor, it will invoke object with display text, because that "unknown" object cannot be edited.Ripply
You are right, the problem is I have kind of recursion here. But I actually don't see any reason why ASP.NET MVC can't resolve it.Emilioemily
Well, first question I would ask, which template it should invoke here? I had the same problem, I had editors which place labels/inputs into li or td elements (depending on if I want list or table), and both render @Html.LabelFor, but I wanted same editor in both (@Html.EditorFor(m=>m, "Editor"), and same problem, cannot call one editor from both templates, so I ended up moving Editor.cshtml (razor extension) one level up, into Views/Shared folder and using @Html.Partial("Editor") instead of @Html.Editor(propertyName) in my editor template, and @Html.Partial("Display") in my display template.Ripply
I expect it to call the editor which it would call in the first place if I didn't specify template name explicitlyEmilioemily
F
1

What type is the "x.SomeProperty"? I'm gonna assume for now it's type is called Property.

MyModel.cs

public class MyModel
{
    public Property SomeProperty { get; set; }
}

Property.cs

public class Property
{
    [Display(Name="Foo")]
    public int Value { get; set; }
}

Views/Shared/EditorTemplates/Property.cshtml

@model MvcApplication1.Models.Property

@Html.LabelFor(model => model.Value)
@Html.EditorFor(model => model.Value)
@Html.ValidationMessageFor(model => model.Value)

MyView.cshtml

@Html.EditorFor(model=>model.SomeProperty)

If you don't provide templatename for EditorFor helper it finds editortemplate which name matches SomeProperty's type.

Update:

To make a custom editortemplate for string you do:

Model

public class MyModel
{
    public string SomeProperty { get; set; }
}

View:

@Html.EditorFor(model => model.SomeProperty,"Property")

Or alternatively:

Model:

public class MyModel
{
    [DataType("Property")]
    public string SomeProperty { get; set; }
}

View:

@Html.EditorFor(model => model.SomeProperty) 

Views/Shared/EditorTemplates/Property.cshtml:

@model string

@Html.Raw("I'm using property editortemplate:")
@Html.Label(ViewData.ModelMetadata.PropertyName)
@Html.Editor(ViewData.ModelMetadata.PropertyName)
@Html.ValidationMessage(ViewData.ModelMetadata.PropertyName)
Futtock answered 30/8, 2012 at 10:58 Comment(3)
SomeProperty's type is string, so I expect textbox to be renderedEmilioemily
I do not need template for string, I need a general template for any propertyEmilioemily
Alright now I get it. Not sure if that's possible. You can though change the "@model string" part with @model object and replace Html.Editor with Html.TextBox and it'll work. It'll be always a textbox though. To be honest I don't see a point for creating editortemplate for generic object. Another workaround is to use partial views instead of editortemplates.Futtock

© 2022 - 2024 — McMap. All rights reserved.