Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET MVC editor template for property

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?

like image 762
SiberianGuy Avatar asked Aug 30 '12 07:08

SiberianGuy


People also ask

What is MVC editor template?

An EditorTemplate is a Razor file placed in the EditorTemplates folder: For Razor Pages apps, in the Pages/Shared/EditorTemplates folder. For MVC apps, in the Views/Shared/EditorTemplates folder or the Views/ControllerName/EditorTemplates folder.

What is display for in MVC?

Display modes in ASP.NET MVC 5 provide a way of separating page content from the way it is rendered on various devices, like web, mobile, iPhone, iPod and Windows Phones. All you need to do is to define a display mode for each device, or class of devices.

What is HTML EditorFor?

The Html. Editor() or Html. EditorFor() extension methods generate HTML elements based on the data type of the model object's property. The following table list the data types and releted HTML elements: DataType.

What is templated HTML helpers in MVC?

It goes through the every property in the model for displaying object. @Html. DisplayFor(modal => modal) :- This Display template helper is used with strongly typed views , if model has properties which return complex objects then this template helper is very useful.


2 Answers

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 :)

like image 150
Goran Obradovic Avatar answered Sep 22 '22 00:09

Goran Obradovic


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)
like image 1
Tomi Lammi Avatar answered Sep 19 '22 00:09

Tomi Lammi