英文:
DisplayNameForModel and DisplayNameFor don't work without setting its DisplayName at the model class definition
问题
I got a model called Foo that is a Bar property.
Then I created a details view
By default `DisplayNameFor` is rendering the name of the property thus, I was expecting the same behavior for the model's name but instead, I have got an empty string. The result is the same even when I use the `DisplayNameForModel`.
I can add a display name to fix that but I don't want to open that door yet. I'm postponing internationalization further to avoid mixing my models with UI stuff.
It's even awkward because it works (without setting DisplayName) when `Foo` model is a property of another class.
Am I missing something or currently there isn't possible to archive that? Can I open a request to the project's maintainers to fix that? Where?
Thanks!
英文:
I got a model called Foo that is a Bar property.
// Foo model
public class Foo
{
public string Bar { get; set; } = "";
}
Then I created a details view
@model Models.Foo
<h1>Details @Html.DisplayNameFor(model => model)</h1>
<dl class="row">
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Bar)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Bar)
</dd>
</dl>
By default DisplayNameFor
is rendering the name of the property thus, I was expecting the same behavior for the model's name but instead, I have got an empty string. The result is the same even when I use the DisplayNameForModel
...
<h1>Details @Html.DisplayNameForModel()</h1>
...
I can add a display name to fix that but I don't want to open that door yet. I'm postponing internationalization further to avoid mixing my models with UI stuff.
// Foo model
[DisplayName("Foo")] // I'm trying to avoid this solution
public class Foo
{
public string Bar { get; set; } = "";
}
It's even awkward because it works (without setting DisplayName) when Foo
model is a property of another class.
Am I missing something or currently there isn't possible to archive that? Can I open a request to the project's maintainers to fix that? Where?
Thanks!
答案1
得分: 1
参考文档中的DisplayNameFor
,expression
参数的描述是:“要针对当前模型计算的表达式。” 可能实现是针对当前模型中的项进行评估,而不是针对模型类本身进行评估。这符合当Foo
是另一个类中的属性时,您会获得Foo
的名称(即您获取属性Foo
的名称而不是类名)的模式。
问题:DisplayName
属性缺失时的不一致性回退
在Microsoft.AspNetCore.Mvc.ViewFeatures
命名空间中内置的DisplayNameFor
HTML助手的预期输出存在不一致性。(https://source.dot.net/#Microsoft.AspNetCore.Mvc.ViewFeatures/HtmlHelperOfT.cs,93a34f38f20458b3)
当为类定义提供DisplayName
属性以及当为类体内的属性类成员提供DisplayName
属性时,DisplayNameFor
HTML助手在这两种情况下都返回DisplayName
属性的值。
[DisplayName("Foo Class")]
public class Foo
{
public string Id { get; set; }
[DisplayName("Bar Property")]
public string Bar { get; set; }
}
当类定义或属性类成员未提供DisplayName
属性时,DisplayNameFor
HTML助手对于类定义返回空字符串,但对于类体内的属性返回属性的名称。类定义的预期结果应该回退到类定义的名称,就像DisplayNameFor
对待类体内的属性名称一样。
public class Foo // HTML助手返回空字符串
{
public string Id { get; set; }
public string Bar { get; set; } // HTML助手回退为'Bar'
}
使用反射
在Razor页面中显示模型的名称的另一种方法是使用Reflection中的GetType()
方法。
@Model.GetType().Name
Name
属性获取了您从@Html.DisplayNameFor(model => model)
期望的标签。
如果GetType()
无法正确解析,可能需要在视图页面的顶部添加@using System.Reflection
。
添加自定义HTML助手
(2023年2月27日的代码添加,以解决@jbatista的评论)
按照这个Stack Overflow答案中的示例添加自定义标签助手。
使用GetCustomAttribute
来获取显示名称属性的值。这个Microsoft文档页面提供了一个很好的示例。
从上面引用的两个链接中提取代码,以下是一个在Razor页面中获取模型的[DisplayName("name")]
属性值的自定义HTML助手。
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)
{
// 获取'DisplayName'属性的实例
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>");
}
}
}
在WebApplication1.Data命名空间中的Razor页面(DisplayNameForModelHtmlHelper.cshtml
) - 可以根据Stack Overflow答案将@using和@addTagHelper语句添加到_ViewImports.cshtml
文件。
@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()
{
}
}
}
渲染的HTML
Foo bar
Description of foo
您原来的代码没有输出预期的结果:
<h1>Details @Html.DisplayNameForModel()</h1>
可以使用自定义HTML助手替换以获得预期的结果:
<h1>Details @Html.DisplayNameForModel2(Model)</h1>
接受的答案
(基于@dave之前的答案部分)
我创建了一个DisplayNameFor
扩展方法,而不是DisplayNameForModel2
,以获得一致的DSL,因为它是用于模型属性的方法名称。
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();
}
}
然后可以这样调用它:
<h1>Create @Html.DisplayNameFor(Model)</h1>
下一步是将其隔离到自己的项目中(Microsoft.AspNetCore.Mvc.Rendering.MissingMethods?),同时弄清楚是否有必要向项目维护者报告这个问题 - 我们不应该默认获取这样的东西吗?
英文:
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?
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论