从资源文件中获取DisplayName属性?

169

我有一个本地化的应用程序,我想知道是否可以将某个模型属性的DisplayName设置为来自资源文件的值。

我希望实现类似下面这样的操作:

public class MyModel {
  [Required]
  [DisplayName(Resources.Resources.labelForName)]
  public string name{ get; set; }
}

但是编译器提示: "属性参数必须是常量表达式、typeof 表达式或属性参数类型的数组创建表达式",因此我无法这样做 :(

是否有任何解决方法?我正在手动输出标签,但我需要这些来验证输出!

6个回答

388
如果您使用的是 MVC 3 和 .NET 4,您可以在 System.ComponentModel.DataAnnotations 命名空间中使用新的Display 属性。该属性替换了 DisplayName 属性,并提供了更多功能,包括本地化支持。
在您的情况下,您可以像这样使用它:
public class MyModel
{
    [Required]
    [Display(Name = "labelForName", ResourceType = typeof(Resources.Resources))]
    public string name{ get; set; }
}
作为附注,这个属性将不适用于 App_GlobalResourcesApp_LocalResources 中的资源。这与这些资源使用的自定义工具(GlobalResourceProxyGenerator)有关。相反,请确保将您的资源文件设置为“嵌入式资源”,并使用“ResXFileCodeGenerator”自定义工具。(另外,你不应该在MVC中使用App_GlobalResourcesApp_LocalResources。你可以在这里了解更多原因)。

这对于此处使用的特定示例很好,但对于大多数想要字符串的动态属性设置器不起作用。 - kingdango
1
当我们在部署到生产之前拥有所有本地化文件时,这种方法非常好。但是,如果我想要从数据库中调用字符串,那么这种方法就不太合适了。而且资源文件的缺点是它需要重新编译才能影响更改,这意味着您需要发布另一个版本给客户端。我并不认为这是一种不好的方法,请不要误解。 - kbvishnu
只是一个问题:我们必须在模型中知道资源类型。我有一个模型DLL和一个网站,分别位于两个不同的项目中。我想能够在模型中设置显示名称并在网站中设置资源类型... - Kek
1
获取Resharper,并在您的项目中创建资源文件,它会自动提醒并帮助您将名称移动到资源文件中。 - anIBMer
17
在C# 6中,你可以使用Name = nameof(Resources.Resources.labelForName)代替Name = "labelForName"。这样做不仅使代码更易读,而且可以在重构时帮助减少因为错误字符串而引起的问题。 - Uwe Keim
显示剩余4条评论

115

那你可以写一个自定义属性:

public class LocalizedDisplayNameAttribute: DisplayNameAttribute
{
    public LocalizedDisplayNameAttribute(string resourceId) 
        : base(GetMessageFromResource(resourceId))
    { }

    private static string GetMessageFromResource(string resourceId)
    {
        // TODO: Return the string from the resource file
    }
}

这可以像这样使用:

public class MyModel 
{
    [Required]
    [LocalizedDisplayName("labelForName")]
    public string Name { get; set; }
}

26
在此帖下方(获得最多票数的帖子下面),@Gunder发布了一个更好的解决方案,这只是为那些只看已被接受答案的人准备的。 - 321X
1
这种方法无法访问不同的翻译,因为它将返回相同的值,无论用户是谁。请将resourceid存储在本地变量中,并覆盖DisplayName。 - Fischer
5
TODO的建议:返回Resources.Language.ResourceManager.GetString(resourceId)。 - Leonel Sanches da Silva
4
您应该使用以下方式进行描述:[Display(Name = "labelForName", ResourceType = typeof(Resources.Resources))] - Frank Boucher
说实话,如果省略内置的ASP.NET属性中的ResourceType = typeof(Resources.Resources)部分,我的模型会更易读...我希望微软能够提供一个静态属性来设置所有属性的默认ResourceType。我的意思是,我几乎想不出我所做过的任何项目需要使用多个ResourceType...即使需要这样做,他也可以始终覆盖默认值。这肯定比在缺少ResourceType时抛出错误要好得多。 - NoOne
显示剩余6条评论

39

如果您打开资源文件并将访问修饰符更改为public或internal,则会从资源文件生成一个类,使您能够创建强类型的资源引用。

资源文件代码生成选项

这意味着您可以像这样做(使用C# 6.0)。 然后您就不必记住 firstname 是小写还是驼峰式大小写。并且您可以查看其他属性是否使用相同的资源值,并使用“查找所有引用”功能。

[Display(Name = nameof(PropertyNames.FirstName), ResourceType = typeof(PropertyNames))]
public string FirstName { get; set; }

这个能在Winforms中工作吗?我有一个类,我从资源中添加了Display注释,并使用了链接中的GetAttributeFrom方法来获取属性的名称,但它没有显示本地化的名称! - Dark_Knight
3
你是使用 attribute.Name 或 attribute.GetName() 来获取本地化文本?.Name 的文档说明:“不要使用此属性来获取 Name 属性的值。请改用 GetName 方法。” https://msdn.microsoft.com/zh-cn/library/system.componentmodel.dataannotations.displayattribute.name(v=vs.110).aspx - Tikall
是的,我明白了,我必须使用GetName()。谢谢。 - Dark_Knight

20

更新:

我知道现在已经有些晚了,但我想补充一下这个更新:

我正在使用由Phil Haacked提供的约定模型元数据提供程序,它更加强大且易于应用。你可以去看一下这里: ConventionalModelMetadataProvider


旧答案

如果你想要支持多种类型的资源,可以这样做:

public class LocalizedDisplayNameAttribute : DisplayNameAttribute
{
    private readonly PropertyInfo nameProperty;

    public LocalizedDisplayNameAttribute(string displayNameKey, Type resourceType = null)
        : base(displayNameKey)
    {
        if (resourceType != null)
        {
            nameProperty = resourceType.GetProperty(base.DisplayName,
                                           BindingFlags.Static | BindingFlags.Public);
        }
    }

    public override string DisplayName
    {
        get
        {
            if (nameProperty == null)
            {
                return base.DisplayName;
            }
            return (string)nameProperty.GetValue(nameProperty.DeclaringType, null);
        }
    }
}

然后像这样使用它:

    [LocalizedDisplayName("Password", typeof(Res.Model.Shared.ModelProperties))]
    public string Password { get; set; }

完整的本地化教程请参见此页面


1
+1. Haack的解决方案绝对是与其他方案相比最优雅的。它非常适合ASP.NET MVC中基于约定的编程风格,并且可以通过单个nuget命令和Global.asax.cs中的一行代码轻松实现。 - Nilzor

11

通过选择资源属性并将"Custom Tool"切换为"PublicResXFileCodeGenerator"以及将构建操作设置为"Embedded Resource",我成功地使Gunder的答案与我的App_GlobalResources一起工作。

请注意下面Gunder的评论。

输入图片说明

非常好用 :)


这将生成一个资源文件,就像您添加了一个位于 App_GlobalResources 之外的资源文件一样进行编译。因此,您的资源文件将不再表现为“App_GlobalResources”资源文件,这完全没有问题。但是您应该知道这一点。因此,您不再具有将资源文件放在 App_GlobalResources 中的“好处”。您可以将其放在其他地方。 - René

6
public class Person
{
    // Before C# 6.0
    [Display(Name = "Age", ResourceType = typeof(Testi18n.Resource))]
    public string Age { get; set; }

    // After C# 6.0
    // [Display(Name = nameof(Resource.Age), ResourceType = typeof(Resource))]
}
  1. 定义属性的ResourceType,以便查找资源。
  2. 定义属性的Name,用于资源的关键字。在C# 6.0之后,您可以使用nameof进行强类型支持,而不是硬编码关键字。

  3. 在控制器中设置当前线程的文化。

Resource.Culture = CultureInfo.GetCultureInfo("zh-CN");

  1. 将资源的可访问性设置为public。

  2. cshtml中显示标签,如下所示:

@Html.DisplayNameFor(model => model.Age)


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