DisplayNameForModel and DisplayNameFor don't work without setting its DisplayName at the model class definition

huangapple go评论59阅读模式
英文:

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

参考文档中的DisplayNameForexpression参数的描述是:“要针对当前模型计算的表达式。” 可能实现是针对当前模型中的进行评估,而不是针对模型类本身进行评估。这符合当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(&quot;Foo Class&quot;)]
public class Foo
{
    public string Id { get; set; }

    [DisplayName(&quot;Bar Property&quot;)]
    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 &#39;Bar&#39; 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 =&gt; 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(&quot;name&quot;)] 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 &#39;DisplayName&#39; attribute
            DisplayNameAttribute? displayNameAttr =
                (DisplayNameAttribute?)Attribute.GetCustomAttribute(
                    model.GetType(), typeof(DisplayNameAttribute));

            string displayName = displayNameAttr == null ?
                  model.GetType().Name :
                  displayNameAttr.DisplayName.ToString();

            return new HtmlString($&quot;&lt;strong&gt;{displayName}&lt;/strong&gt;&quot;);
        }
    }
}

namespace WebApplication1.Data
{
    [DisplayName(&quot;Foo bar&quot;)]
    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
@{
}
&lt;div&gt;@Html.DisplayNameForModel2(Model.Foo)&lt;/div&gt;
@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 = &quot;An ID&quot;,
            Description = &quot;Description of foo&quot;
        };

        public void OnGet()
        {
        }
    }
}

Rendered HTML

Foo bar
Description of foo

Your original code that didn't output the expected result:

&lt;h1&gt;Details @Html.DisplayNameForModel()&lt;/h1&gt;

can be replaced with a custom HTML Helper to get the expected result:

&lt;h1&gt;Details @Html.DisplayNameForModel2(Model)&lt;/h1&gt;

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:

&lt;h1&gt;Create @Html.DisplayNameFor(Model)&lt;/h1&gt;

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?

huangapple
  • 本文由 发表于 2023年2月14日 19:27:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/75447171.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定