如何在不使用`DisplayNameAttribute`的情况下更改ViewModel显示名称?

4
我想直接更改ViewModel中某些属性的显示名称,而不使用[DisplayName("prop name")]。这应该在控制器内部返回View之前或者在ViewModel类本身内部完成。
我不想在View中更改任何内容,也不想使用任何数据注释。我该如何实现?
可能有一些流畅语法可以解决吗?
我正在使用:ASP.Net Core 2.0
数据注释的问题在于我希望在运行时获取我的显示名称(而数据注释是预编译的)。
更新:
提出这个问题的主要原因是要找到一种方法来封装IStringLocalizer,特别是在本地化数据注释时的行为。被接受的答案很好地解释了这个基础知识。

为什么不在视图模型上拥有标签属性,并在视图中使用它,这样你就可以从控制器操作中分配标签。 - Joe Audette
@JoeAudette,这正是我想要做的。我只是在寻找更加优雅的方式(如果有的话),让属性及其名称“耦合”在一起。 - Mohammed Noureldin
@Tseng,本地化正是我的问题。我不太喜欢 .Net Core 的本地化解决方案。它的主要问题在于,我不喜欢所有属性都从同一个文件中本地化。如果我能够准确地指定应该从哪个文件本地化哪个属性,那就太完美了。因此,我想实现自己的本地化中间件(甚至只是一个静态类本地化解决方案),但我仍然无法本地化 DisplayName - Mohammed Noureldin
1
如果您对默认实现不满意,可以覆盖IDisplayMetadataProvider的默认实现来完成此操作。这里是去年关于DisplayNameAttribute最简示例,但您可以在其中选择要使用的类/资源文件。 - Tseng
是的,请帮助我们理解为什么您想要在控制器内部执行此操作? - dev8989
显示剩余6条评论
1个回答

8
@Tseng,抱歉我当时没有说清楚,我的意思是我们应该使用命名约定或共享资源文件,但不能同时使用。在许多情况下,我有很多共享资源和许多ViewModel特定的字符串(混合使用)。这在.NET Core本地化解决方案中是不可行的。
如果你的唯一担忧是无法确定选择了一个或多个资源文件,那么这可以很容易地配置。我不得不深入挖掘源代码,但它似乎是可能的。
正如我们所见这里localizer是由配置中定义的工厂决定的。
if (_stringLocalizerFactory != null && _localizationOptions.DataAnnotationLocalizerProvider != null)
{
    localizer = _localizationOptions.DataAnnotationLocalizerProvider(containerType, _stringLocalizerFactory);
}

_localizationOptionsMvcDataAnnotationsLocalizationOptions

MvcDataAnnotationsLocalizationOptions的默认实现在这里

/// <inheritdoc />
public void Configure(MvcDataAnnotationsLocalizationOptions options)
{
    if (options == null)
    {
        throw new ArgumentNullException(nameof(options));
    }

    options.DataAnnotationLocalizerProvider = (modelType, stringLocalizerFactory) =>
        stringLocalizerFactory.Create(modelType);
}

默认情况下,它使用每个模型的资源。

如果您愿意,可以通过以下方法将其更改为所有数据注释使用一个SharedResource文件,在您的Startup.ConfigureServices中添加以下内容(未经测试,但应该可行):

services.AddMvc()
    .AddDataAnnotationsLocalization(options =>
    {
        options.DataAnnotationLocalizerProvider = (type, factory) =>
            factory.Create(typeof(SharedResource));
    });

这将有效地忽略传递的类型并始终返回共享字符串本地化程序。

当然,您可以添加任何逻辑,并在每种类型的情况下决定要使用哪个本地化程序。

编辑

如果这还不够,您可以实现自己的自定义IDisplayMetadataProvider,以您想要的方式处理它。但实际上使用 DisplayAttribute 就足够了。 DisplayAttribute有其他参数,允许您定义资源类型。

[Display(Name = "StringToLocalize", ResourceType = typeof(SharedResource))]

使用ResourceType,您可以选择用于查找本地化的类(因此也是资源文件名)。
编辑2:使用包装的IStringLocalizer,并回退到每个视图模型资源
更优雅的解决方案涉及使用上述MvcDataAnnotationsLocalizationOptions选项文件返回自己的IStringLocalizer,该IStringLocalizer查看一个资源文件并回退到另一个资源文件。
public class DataAnnotationStringLocalizer : IStringLocalizer
{
    private readonly IStringLocalizer primaryLocalizer;
    private readonly IStringLocalizer fallbackLocalizer;

    public DataAnnotationStringLocalizer(IStringLocalizer primaryLocalizer, IStringLocalizer fallbackLocalizer)
    {
        this.primaryLocalizer = primaryLocalizer ?? throw new ArgumentNullException(nameof(primaryLocalizer));
        this.fallbackLocalizer = fallbackLocalizer ?? throw new ArgumentNullException(nameof(fallbackLocalizer));
    }

    public LocalizedString this[string name]
    {
        get
        {
            LocalizedString localizedString = primaryLocalizer[name];
            if (localizedString.ResourceNotFound)
            {
                localizedString = fallbackLocalizer[name];
            }

            return localizedString;
        }
    }

    public LocalizedString this[string name, params object[] arguments]
    {
        get
        {
            LocalizedString localizedString = primaryLocalizer[name, arguments];
            if (localizedString.ResourceNotFound)
            {
                localizedString = fallbackLocalizer[name, arguments];
            }

            return localizedString;
        }
    }

    public IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures)
        => primaryLocalizer.GetAllStrings(includeParentCultures).Concat(fallbackLocalizer.GetAllStrings(includeParentCultures));

    public IStringLocalizer WithCulture(CultureInfo culture)
        => new DataAnnotationStringLocalizer(primaryLocalizer.WithCulture(culture), fallbackLocalizer.WithCulture(culture));
}

以下是相关选项:
services.AddMvc()
    .AddDataAnnotationsLocalization(options =>
    {
        options.DataAnnotationLocalizerProvider = (type, factory) =>
        {
            return new DataAnnotationStringLocalizer(
                factory?.Create(typeof(SharedResource)),
                factory?.Create(type)
            );
        };
    });

现在,字符串首先从共享资源中解析,如果在那里找不到该字符串,则会从视图模型类型(传递给工厂方法的类型参数)中解析它。
如果您不喜欢这种逻辑,并且希望它首先查找视图模型资源文件,您只需更改顺序即可。
services.AddMvc()
    .AddDataAnnotationsLocalization(options =>
    {
        options.DataAnnotationLocalizerProvider = (type, factory) =>
        {
            return new DataAnnotationStringLocalizer(
                factory?.Create(type),
                factory?.Create(typeof(SharedResource))
            );
        }
    });

现在视图模型是主要的解析器,共享资源是次要的。

1
是的,我刚刚在做一个额外的例子,你可以在其中创建一个自定义的本地化程序,它接受两个本地化程序,一个用于共享资源,另一个用于模型。然后,你会检查字符串是否存在于共享资源中,如果不存在,则在视图模型资源中查找。 - Tseng
1
请参考此示例 https://github.com/aspnet/Mvc/issues/2706#issuecomment-248049748。该示例已过时,因为 DisplayNameAttribute 代码现在已经移动到了 DataAnnotationsMetadataProvider 中,但它展示了如何添加和注册自己的 IXxxMetadataProvider - Tseng
1
@MohammedNoureldin:它们有些不同。DisplayNameAttribute没有DisplayName的属性,只有一个无参数构造函数和Name。而DisplayAttributeNameDescriptionResourceType等。但现在两者都已经在DataAnnotationsMetadataProvider中实现了(这在发布ASP.NET Core 1.0时不是这样的)。 - Tseng
1
更新了我的回答。除了“它可以编译”之外,还没有经过测试;) 下次请在初始问题中提供足够的信息,这样更容易引入解决方案,以避免不想使用ASP.NET Core本地化的确切原因。本地化系统非常灵活和可定制。 - Tseng
1
@MohammedNoureldin:两者都没有使用非托管资源,所以没有需要处理和担心的内容。ResourceManagerStringLocalizerStringLocalizer都没有实现IDisposable。M.E.DI在IDisposable被实现并通过M:E.DI解决时调用dispose,所有其他内容将在它们不再被引用且GC感觉适合时被处理。 - Tseng
显示剩余10条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接