Referencing the documentation for DisplayNameFor
, the description for the expression
parameter says: "An expression to be evaluated against the current model." It's possible that the implementation is evaluated against an item in the current model and not against the model class itself. This fits the pattern where you get the name of Foo
when it's a property in another class (i.e. you're getting the name of the property named Foo
versus the class name).
Issue: Inconsistent fallback when DisplayName
attribute is missing
There's an inconsistency in the expected output of the built-in DisplayNameFor
HTML Helper in the Microsoft.AspNetCore.Mvc.ViewFeatures
namespace. (https://source.dot.net/#Microsoft.AspNetCore.Mvc.ViewFeatures/HtmlHelperOfT.cs,93a34f38f20458b3)
When a DisplayName
attribute is provided for a class definition and when a DisplayName
attribute is provided for a property class member within the class body, the DisplayNameFor
HTML Helper returns the value for the DisplayName
attribute in both cases.
[DisplayName("Foo Class")]
public class Foo
{
public string Id { get; set; }
[DisplayName("Bar Property")]
public string Bar { get; set; }
}
When a DisplayName
attribute is not provided for the class definition or a property class member, the DisplayNameFor
HTML Helper returns an empty string for the class definition but returns the name of the property within the class body. The expected result for the class definition is to fallback to the name of the class definition as DisplayNameFor
does with the name of the property within the class body.
public class Foo // HTML Helper returns empty string
{
public string Id { get; set; }
public string Bar { get; set; } // Fallback to 'Bar' in HTML Helper
}
Using Reflection
An alternate method to display the name of the model in a Razor page, the GetType()
method from Reflection works.
@Model.GetType().Name
The Name
property gets the same label you're expecting from @Html.DisplayNameFor(model => model)
.
Adding @using System.Reflection
to the top of the view page may be necessary if the GetType()
does not resolve correctly.
Adding Custom HTML Helper
(2/27/2023 Code addition to address @jbatista's comment)
Add a custom tag helper as this Stack Overflow answer outlines.
Use GetCustomAttribute
to get the value of the display name attribute. This Microsoft documentation page provides a good example.
Pulling code from the two links referenced above, here's a custom HTML Helper that gets the value of the [DisplayName("name")]
attribute for a model in a Razor page.
MyHTMLHelpers.cs
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Rendering;
using System.ComponentModel;
namespace WebApplication1.Helpers
{
// https://stackoverflow.com/questions/42039269/create-custom-html-helper-in-asp-net-core
public static class MyHTMLHelpers
{
public static IHtmlContent DisplayNameForModel2(
this IHtmlHelper htmlHelper, object model)
{
// Retrieving a Single Instance of an Attribute
// https://learn.microsoft.com/en-us/dotnet/standard/attributes/retrieving-information-stored-in-attributes#retrieving-a-single-instance-of-an-attribute
// Get instance of the 'DisplayName' attribute
DisplayNameAttribute? displayNameAttr =
(DisplayNameAttribute?)Attribute.GetCustomAttribute(
model.GetType(), typeof(DisplayNameAttribute));
string displayName = displayNameAttr == null ?
model.GetType().Name :
displayNameAttr.DisplayName.ToString();
return new HtmlString($"<strong>{displayName}</strong>");
}
}
}
namespace WebApplication1.Data
{
[DisplayName("Foo bar")]
public class Foo
{
public string Id { get; set; }
public string Description { get; set; }
}
}
Razor page (DisplayNameForModelHtmlHelper.cshtml
) - The @using and @addTagHelper statements can be added to a _ViewImports.cshtml
file per the Stack Overflow answer.
@page
@using WebApplication1
@using WebApplication1.Helpers
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@model WebApplication1.Pages.DisplayNameForModelHtmlHelperModel
@{
}
<div>@Html.DisplayNameForModel2(Model.Foo)</div>
@Model.Foo.Description
DisplayNameForModelHtmlHelper.cshtml.cs
using Microsoft.AspNetCore.Mvc.RazorPages;
using WebApplication1.Data;
namespace WebApplication1.Pages
{
public class DisplayNameForModelHtmlHelperModel : PageModel
{
public Foo Foo { get; set; } = new Foo()
{
Id = "An ID",
Description = "Description of foo"
};
public void OnGet()
{
}
}
}
Rendered HTML
Foo bar
Description of foo
Your original code that didn't output the expected result:
<h1>Details @Html.DisplayNameForModel()</h1>
can be replaced with a custom HTML Helper to get the expected result:
<h1>Details @Html.DisplayNameForModel2(Model)</h1>
Accepted answer
(@jbatista base on @dave previous answer section)
I create a DisplayNameFor extension method instead of DisplayNameForModel2 to have a consistent DSL since it's the method name used for the model's attributes.
public static class AspNetCoreMvcRenderingMissingExtentionMethods
{
public static string DisplayNameFor(this IHtmlHelper htmlHelper, object model)
{
DisplayNameAttribute? displayNameAttr = (DisplayNameAttribute?)Attribute.GetCustomAttribute(model.GetType(), typeof(DisplayNameAttribute));
return displayNameAttr == null ? model.GetType().Name : displayNameAttr.DisplayName.ToString();
}
}
Then I can call it like that:
<h1>Create @Html.DisplayNameFor(Model)</h1>
The next step is to isolate this into its own project (Microsoft.AspNetCore.Mvc.Rendering.MissingMethods?) while figuring out if makes sense to report that to the project maintainers - shouldn't we get something like that by default?