Localizzazione di DisplayNameAttribute

Sto cercando un modo per localizzare i nomi delle proprietà visualizzati in un PropertyGrid. Il nome della proprietà può essere “sovrascritto” utilizzando l’attributo DisplayNameAttribute. Sfortunatamente gli attributi non possono avere espressioni non costanti. Quindi non posso usare risorse fortemente tipizzate come:

class Foo { [DisplayAttribute(Resources.MyPropertyNameLocalized)] // do not compile string MyProperty {get; set;} } 

Ho dato un’occhiata e ho trovato alcuni suggerimenti da ereditare da DisplayNameAttribute per poter usare la risorsa. Vorrei finire con codice come:

 class Foo { [MyLocalizedDisplayAttribute("MyPropertyNameLocalized")] // not strongly typed string MyProperty {get; set;} } 

Tuttavia, perdo i benefici delle risorse fortemente tipizzati che non sono sicuramente una buona cosa. Poi ho trovato DisplayNameResourceAttribute che potrebbe essere quello che sto cercando. Ma dovrebbe essere nello spazio dei nomi Microsoft.VisualStudio.Modeling.Design e non riesco a trovare quale riferimento devo aggiungere per questo spazio dei nomi.

Qualcuno sa se c’è un modo più semplice per raggiungere la localizzazione DisplayName in un modo buono? o se c’è modo di usare ciò che Microsoft sembra usare per Visual Studio?

Esiste l’ attributo Display di System.ComponentModel.DataAnnotations in .NET 4. Funziona sul PropertyGrid MVC 3.

 [Display(ResourceType = typeof(MyResources), Name = "UserName")] public string UserName { get; set; } 

MyResources una risorsa denominata UserName nel tuo file MyResources .

Lo stiamo facendo per una serie di attributi per supportare più lingue. Abbiamo adottato un approccio simile a Microsoft, in cui sostituiscono gli attributi di base e passano il nome di una risorsa anziché la stringa effettiva. Il nome della risorsa viene quindi utilizzato per eseguire una ricerca nelle risorse DLL per la stringa effettiva da restituire.

Per esempio:

 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); } } } 

È ansible fare un ulteriore passo avanti quando si utilizza effettivamente l’attributo e specificare i nomi delle risorse come costanti in una class statica. In questo modo, ottieni dichiarazioni come.

 [LocalizedDisplayName(ResourceStrings.MyPropertyName)] public string MyProperty { get { ... } } 

Aggiornare
ResourceStrings avrebbe un aspetto simile (nota, ogni stringa farebbe riferimento al nome di una risorsa che specifica la stringa effettiva):

 public static class ResourceStrings { public const string ForegroundColorDisplayName="ForegroundColorDisplayName"; public const string FontSizeDisplayName="FontSizeDisplayName"; } 

Ecco la soluzione che ho trovato in un assembly separato (chiamato “Common” nel mio caso):

  [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event)] public class DisplayNameLocalizedAttribute : DisplayNameAttribute { public DisplayNameLocalizedAttribute(Type resourceManagerProvider, string resourceKey) : base(Utils.LookupResource(resourceManagerProvider, resourceKey)) { } } 

con il codice per cercare la risorsa:

  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 } 

L’uso tipico sarebbe:

 class Foo { [Common.DisplayNameLocalized(typeof(Resources.Resource), "CreationDateDisplayName"), Common.DescriptionLocalized(typeof(Resources.Resource), "CreationDateDescription")] public DateTime CreationDate { get; set; } } 

Ciò che è abbastanza brutto quando uso le stringhe letterali per la chiave risorsa. Utilizzando una costante ci vorrebbe modificare Resources.Designer.cs che probabilmente non è una buona idea.

Conclusione: non ne sono felice, ma sono ancora meno felice di Microsoft che non può fornire nulla di utile per un compito così comune.

È ansible utilizzare T4 per generare costanti. Ne ho scritto uno:

 <#@ 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 { ///  /// Localized display name attribute ///  public class LocalizedDisplayNameAttribute : DisplayNameAttribute { readonly string _resourceName; ///  /// Initializes a new instance of the  class. ///  /// Name of the resource. public LocalizedDisplayNameAttribute(string resourceName) : base() { _resourceName = resourceName; } ///  /// Gets the display name for a property, event, or public void method that takes no arguments stored in this attribute. ///  ///  ///  /// The display name. ///  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#>"; <# } #> } } } 

Usando l’attributo Display (da System.ComponentModel.DataAnnotations) e l’espressione nameof () in C # 6, otterrai una soluzione localizzata e fortemente tipizzata.

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

Questa è una vecchia domanda, ma penso che questo sia un problema molto comune, ed ecco la mia soluzione in MVC 3.

In primo luogo, è necessario un modello T4 per generare costanti per evitare stringhe cattive. Abbiamo un file di risorse ‘Labels.resx’ contiene tutte le stringhe di etichette. Pertanto il modello T4 usa direttamente il file di risorse,

 <#@ 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(); 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#>"; <# } #> } } 

Quindi, viene creato un metodo di estensione per localizzare il ‘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; } } } 

L’attributo “DisplayName” è sostituito dall’attributo “DisplayLabel” per leggere automaticamente “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; } } } } 

Dopo tutti questi lavori di preparazione, è ora di toccare quegli attributi di convalida predefiniti. Sto usando l’attributo ‘Richiesto’ come esempio,

 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()); } } } 

Ora, possiamo applicare quegli attributi nel nostro modello,

 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; } } } 

Per impostazione predefinita, il nome della proprietà viene utilizzato come chiave per cercare “Label.resx”, ma se lo si imposta tramite “DisplayLabel”, verrà utilizzato al suo posto.

Puoi sottoclass DisplayNameAttribute per fornire i18n, sovrascrivendo uno dei metodi. Così. modifica: potresti dover accontentarti di usare una costante per la chiave.

 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" } } } }); } } 

Io uso in questo modo risolvere nel mio caso

 [LocalizedDisplayName("Age", NameResourceType = typeof(RegistrationResources))] public bool Age { get; set; } 

Con il codice

 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); } } } 

Bene, l’assembly è Microsoft.VisualStudio.Modeling.Sdk.dll . che viene fornito con Visual Studio SDK (con Visual Studio Integration Package).

Ma sarebbe usato più o meno allo stesso modo del tuo attributo; non c’è modo di usare fortemente tipi di risorse negli attributi semplicemente perché non sono costanti.

Mi scuso per il codice VB.NET, il mio C # è un po ‘arrugginito … Ma tu avrai l’idea, giusto?

Prima di tutto, crea una nuova class: LocalizedPropertyDescriptor , che eredita PropertyDescriptor . Sostituisci la proprietà DisplayName in questo modo:

 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 è il ResourceManager del file di risorse che contiene le tue traduzioni.

Quindi, implementare ICustomTypeDescriptor nella class con le proprietà localizzate e sovrascrivere il metodo 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 

Ora puoi utilizzare l’attributo ‘DisplayName` per memorizzare un riferimento a un valore in un file di risorse …

  _ Public Property Description() As String 

prop_description è la chiave nel file di risorse.