DisplayNameAttribute的本地化
我正在寻找一种方法来本地化PropertyGrid中显示的属性名称。 该属性的名称可以使用DisplayNameAttribute属性“覆盖”。 不幸的是,属性不能有非常量expression式。 所以我不能使用强types的资源,如:
class Foo { [DisplayAttribute(Resources.MyPropertyNameLocalized)] // do not compile string MyProperty {get; set;} }
我看了一下,发现一些build议从DisplayNameAttributeinheritance,以便能够使用资源。 我最终会得到如下的代码:
class Foo { [MyLocalizedDisplayAttribute("MyPropertyNameLocalized")] // not strongly typed string MyProperty {get; set;} }
但是,我失去了强烈的types资源利益,这绝对不是一件好事。 然后我碰到DisplayNameResourceAttribute这可能是我正在寻找。 但它应该是在Microsoft.VisualStudio.Modeling.Design命名空间,我无法find我应该为这个命名空间添加什么参考。
有人知道是否有一个更好的方法来实现DisplayName本地化的更简单的方法? 或者如果有什么方法可以使用微软似乎用于Visual Studio的?
在.NET 4中有来自System.ComponentModel.DataAnnotations的Display属性 。它在MVC 3 PropertyGrid
。
[Display(ResourceType = typeof(MyResources), Name = "UserName")] public string UserName { get; set; }
这会在MyResources
文件中查找名为UserName
的资源。
为了支持多种语言,我们正在为此提供一些属性。 我们采取了类似的方法来微软,他们重写他们的基本属性,并传递一个资源名称,而不是实际的string。 然后使用资源名称在DLL资源中执行查找以返回实际字符串。
例如:
class LocalizedDisplayNameAttribute : DisplayNameAttribute { private readonly string resourceName; public LocalizedDisplayNameAttribute(string resourceName) : base() { this.resourceName = resourceName; } public override string DisplayName { get { return Resources.ResourceManager.GetString(this.resourceName); } } }
实际使用该属性时,可以进一步考虑这一点,并将您的资源名称指定为静态类中的常量。 那样的话,你会得到像这样的声明。
[LocalizedDisplayName(ResourceStrings.MyPropertyName)] public string MyProperty { get { ... } }
更新
ResourceStrings
看起来像这样(注意,每个string都会引用指定实际string的资源的名称):
public static class ResourceStrings { public const string ForegroundColorDisplayName="ForegroundColorDisplayName"; public const string FontSizeDisplayName="FontSizeDisplayName"; }
下面是我在一个单独的程序集(在我的例子中称为“Common”)结束的解决scheme:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event)] public class DisplayNameLocalizedAttribute : DisplayNameAttribute { public DisplayNameLocalizedAttribute(Type resourceManagerProvider, string resourceKey) : base(Utils.LookupResource(resourceManagerProvider, resourceKey)) { } }
用代码来查找资源:
internal static string LookupResource(Type resourceManagerProvider, string resourceKey) { foreach (PropertyInfo staticProperty in resourceManagerProvider.GetProperties(BindingFlags.Static | BindingFlags.NonPublic)) { if (staticProperty.PropertyType == typeof(System.Resources.ResourceManager)) { System.Resources.ResourceManager resourceManager = (System.Resources.ResourceManager)staticProperty.GetValue(null, null); return resourceManager.GetString(resourceKey); } } return resourceKey; // Fallback with the key name }
典型的用法是:
class Foo { [Common.DisplayNameLocalized(typeof(Resources.Resource), "CreationDateDisplayName"), Common.DescriptionLocalized(typeof(Resources.Resource), "CreationDateDescription")] public DateTime CreationDate { get; set; } }
什么是非常丑陋的,因为我使用文字string资源键。 使用常量将意味着修改Resources.Designer.cs,这可能不是一个好主意。
结论:我对此并不满意,但是对于微软来说,这样的共同任务不能提供任何有用的东西,
你可以用T4来产生常量。 我写了一个:
<#@ template debug="false" hostspecific="true" language="C#" #> <#@ output extension=".cs" #> <#@ assembly name="System.Xml.dll" #> <#@ import namespace="System.Xml" #> <#@ import namespace="System.Xml.XPath" #> using System; using System.ComponentModel; namespace Bear.Client { /// <summary> /// Localized display name attribute /// </summary> public class LocalizedDisplayNameAttribute : DisplayNameAttribute { readonly string _resourceName; /// <summary> /// Initializes a new instance of the <see cref="LocalizedDisplayNameAttribute"/> class. /// </summary> /// <param name="resourceName">Name of the resource.</param> public LocalizedDisplayNameAttribute(string resourceName) : base() { _resourceName = resourceName; } /// <summary> /// Gets the display name for a property, event, or public void method that takes no arguments stored in this attribute. /// </summary> /// <value></value> /// <returns> /// The display name. /// </returns> public override String DisplayName { get { return Resources.ResourceManager.GetString(this._resourceName); } } } partial class Constants { public partial class Resources { <# var reader = XmlReader.Create(Host.ResolvePath("resources.resx")); var document = new XPathDocument(reader); var navigator = document.CreateNavigator(); var dataNav = navigator.Select("/root/data"); foreach (XPathNavigator item in dataNav) { var name = item.GetAttribute("name", String.Empty); #> public const String <#= name#> = "<#= name#>"; <# } #> } } }
这是一个老问题,但我认为这是一个非常普遍的问题,这里是我在MVC 3中的解决scheme。
首先,需要T4模板来生成常量以避免讨厌的string。 我们有一个资源文件“Labels.resx”包含所有的标签string。 所以T4模板直接使用资源文件,
<#@ template debug="True" hostspecific="True" language="C#" #> <#@ output extension=".cs" #> <#@ Assembly Name="C:\Project\trunk\Resources\bin\Development\Resources.dll" #> <#@ import namespace="System.Collections.Generic" #> <#@ import namespace="System.Collections" #> <#@ import namespace="System.Globalization" #> <#@ import namespace="System" #> <#@ import namespace="System.Resources" #> <# var resourceStrings = new List<string>(); var manager = Resources.Labels.ResourceManager; IDictionaryEnumerator enumerator = manager.GetResourceSet(CultureInfo.CurrentCulture, true, true) .GetEnumerator(); while (enumerator.MoveNext()) { resourceStrings.Add(enumerator.Key.ToString()); } #> // This file is generated automatically. Do NOT modify any content inside. namespace Lib.Const{ public static class LabelNames{ <# foreach (String label in resourceStrings){ #> public const string <#=label#> = "<#=label#>"; <# } #> } }
然后,创build一个扩展方法来定位“DisplayName”
using System.ComponentModel.DataAnnotations; using Resources; namespace Web.Extensions.ValidationAttributes { public static class ValidationAttributeHelper { public static ValidationContext LocalizeDisplayName(this ValidationContext context) { context.DisplayName = Labels.ResourceManager.GetString(context.DisplayName) ?? context.DisplayName; return context; } } }
“DisplayName”属性被replace为“DisplayLabel”属性以自动读取“Labels.resx”
namespace Web.Extensions.ValidationAttributes { public class DisplayLabelAttribute :System.ComponentModel.DisplayNameAttribute { private readonly string _propertyLabel; public DisplayLabelAttribute(string propertyLabel) { _propertyLabel = propertyLabel; } public override string DisplayName { get { return _propertyLabel; } } } }
在所有这些准备工作之后,有时间去触及这些默认validation属性。 我以“Required”属性为例,
using System.ComponentModel.DataAnnotations; using Resources; namespace Web.Extensions.ValidationAttributes { public class RequiredAttribute : System.ComponentModel.DataAnnotations.RequiredAttribute { public RequiredAttribute() { ErrorMessageResourceType = typeof (Errors); ErrorMessageResourceName = "Required"; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { return base.IsValid(value, validationContext.LocalizeDisplayName()); } } }
现在,我们可以在模型中应用这些属性,
using Web.Extensions.ValidationAttributes; namespace Web.Areas.Foo.Models { public class Person { [DisplayLabel(Lib.Const.LabelNames.HowOldAreYou)] public int Age { get; set; } [Required] public string Name { get; set; } } }
默认情况下,使用属性名称作为查找“Label.resx”的键,但是如果通过“DisplayLabel”设置它,它将使用该名称。
在C#6中使用Display属性(来自System.ComponentModel.DataAnnotations)和nameof()expression式,你将得到一个本地化和强types的解决scheme。
[Display(ResourceType = typeof(MyResources), Name = nameof(MyResources.UserName))] public string UserName { get; set; }
你可以通过覆盖DisplayNameAttribute的子类来提供i18n,方法是覆盖其中一个方法。 像这样。 编辑:你可能必须解决使用一个常数的关键。
using System; using System.ComponentModel; using System.Windows.Forms; class Foo { [MyDisplayName("bar")] // perhaps use a constant: SomeType.SomeResName public string Bar {get; set; } } public class MyDisplayNameAttribute : DisplayNameAttribute { public MyDisplayNameAttribute(string key) : base(Lookup(key)) {} static string Lookup(string key) { try { // get from your resx or whatever return "le bar"; } catch { return key; // fallback } } } class Program { [STAThread] static void Main() { Application.Run(new Form { Controls = { new PropertyGrid { SelectedObject = new Foo { Bar = "abc" } } } }); } }
我用这种方式解决我的情况
[LocalizedDisplayName("Age", NameResourceType = typeof(RegistrationResources))] public bool Age { get; set; }
用代码
public sealed class LocalizedDisplayNameAttribute : DisplayNameAttribute { private PropertyInfo _nameProperty; private Type _resourceType; public LocalizedDisplayNameAttribute(string displayNameKey) : base(displayNameKey) { } public Type NameResourceType { get { return _resourceType; } set { _resourceType = value; _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); } } }
那么,程序集是Microsoft.VisualStudio.Modeling.Sdk.dll
。 随Visual Studio SDK一起提供(使用Visual Studio集成包)。
但是它的用法和你的属性差不多, 仅仅因为它们不是恒定的,就没有办法在属性中使用强types资源。
我为VB.NET代码道歉,我的C#有点生疏…但你会明白,对吧?
首先创build一个新的类: LocalizedPropertyDescriptor
,inheritancePropertyDescriptor
。 像这样覆盖DisplayName
属性:
Public Overrides ReadOnly Property DisplayName() As String Get Dim BaseValue As String = MyBase.DisplayName Dim Translated As String = Some.ResourceManager.GetString(BaseValue) If String.IsNullOrEmpty(Translated) Then Return MyBase.DisplayName Else Return Translated End If End Get End Property
Some.ResourceManager
是包含您的翻译的资源文件的ResourceManager。
接下来,在具有本地化属性的类中实现ICustomTypeDescriptor
,并覆盖GetProperties
方法:
Public Function GetProperties() As PropertyDescriptorCollection Implements System.ComponentModel.ICustomTypeDescriptor.GetProperties Dim baseProps As PropertyDescriptorCollection = TypeDescriptor.GetProperties(Me, True) Dim LocalizedProps As PropertyDescriptorCollection = New PropertyDescriptorCollection(Nothing) Dim oProp As PropertyDescriptor For Each oProp In baseProps LocalizedProps.Add(New LocalizedPropertyDescriptor(oProp)) Next Return LocalizedProps End Function
您现在可以使用“DisplayName”属性来存储对资源文件中值的引用…
<DisplayName("prop_description")> _ Public Property Description() As String
prop_description
是资源文件中的关键。