web-dev-qa-db-fra.com

Localisation de DisplayNameAttribute

Je cherche un moyen de localiser les noms de propriétés affichés dans un PropertyGrid. Le nom de la propriété peut être "remplacé" à l'aide de l'attribut DisplayNameAttribute. Malheureusement, les attributs ne peuvent pas avoir d'expressions non constantes. Je ne peux donc pas utiliser de ressources fortement typées telles que:

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

J'ai jeté un coup d'œil et trouvé une suggestion d'hériter de DisplayNameAttribute pour pouvoir utiliser une ressource. Je finirais avec un code comme:

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

Cependant, je perds des ressources très typées, ce qui n’est certainement pas une bonne chose. Puis je suis tombé sur DisplayNameResourceAttribute qui peut être ce que je cherche. Mais il est supposé appartenir à l'espace de noms Microsoft.VisualStudio.Modeling.Design et je ne trouve pas la référence que je suis censé ajouter pour cet espace de noms.

Quelqu'un sait-il s'il existe un moyen plus simple de réaliser la localisation de DisplayName de la bonne manière? ou s'il y a moyen d'utiliser ce que Microsoft semble utiliser pour Visual Studio?

119
PowerKiKi

Voici la solution que j'ai trouvée dans une assemblée séparée (appelée "commune" dans mon cas):

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

avec le code pour rechercher la ressource:

  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'utilisation typique serait:

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

Ce qui est plutôt moche alors que j'utilise des chaînes littérales pour la clé de ressource. Utiliser une constante signifierait modifier Resources.Designer.cs, ce qui n’est probablement pas une bonne idée.

Conclusion: je ne suis pas content de cela, mais je le suis encore moins de Microsoft qui ne peut rien fournir d’utile pour une tâche aussi commune.

41
PowerKiKi

Il y a le attribut d'affichage de System.ComponentModel.DataAnnotations dans .NET 4. Il fonctionne sur le MVC 3 PropertyGrid.

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

Ceci recherche une ressource nommée UserName dans votre fichier MyResources .resx.

111
RandomEngy

Nous le faisons pour un certain nombre d'attributs afin de prendre en charge plusieurs langues. Nous avons adopté une approche similaire à celle de Microsoft, où ils remplacent leurs attributs de base et transmettent un nom de ressource plutôt que la chaîne réelle. Le nom de la ressource est ensuite utilisé pour effectuer une recherche dans les ressources DLL de la chaîne à renvoyer.

Par exemple:

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

Vous pouvez aller plus loin lorsque vous utilisez réellement l'attribut et spécifier vos noms de ressources en tant que constantes dans une classe statique. De cette façon, vous obtenez des déclarations du type.

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

Mise à jour
ResourceStrings ressemblerait à quelque chose comme (note, chaque chaîne ferait référence au nom d'une ressource qui spécifie la chaîne réelle):

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

En utilisant l'attribut Display (de System.ComponentModel.DataAnnotations) et l'expression nameof () en C # 6, vous obtiendrez une solution localisée et fortement typée.

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

Vous pouvez utiliser T4 pour générer des constantes. J'en ai écrit un:

<#@ 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#>";
  <# } #>
  }
 }
}
14
zielu1

C'est une vieille question, mais je pense que c'est un problème très courant, et voici ma solution dans MVC 3.

Tout d'abord, un modèle T4 est nécessaire pour générer des constantes afin d'éviter les chaînes nuisibles. Nous avons un fichier de ressources ‘Labels.resx’ qui contient toutes les chaînes de libellé. Par conséquent, le modèle T4 utilise directement le fichier de ressources.

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

Ensuite, une méthode d’extension est créée pour localiser le "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’attribut "DisplayName" est remplacé par l’attribut "DisplayLabel" pour permettre la lecture automatique de "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;
            }
        }
    }
}

Après tout ce travail de préparation, il est temps de toucher à ces attributs de validation par défaut. J'utilise l'attribut "Obligatoire" comme exemple,

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

    }
}

Maintenant, nous pouvons appliquer ces attributs dans notre modèle,

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

Par défaut, le nom de la propriété est utilisé comme clé pour rechercher "Label.resx", mais si vous le définissez via "DisplayLabel", il l'utilisera à la place.

9
YYFish

Vous pouvez sous-classer DisplayNameAttribute pour fournir i18n en redéfinissant l'une des méthodes. Ainsi. edit: Vous devrez peut-être vous contenter d'une constante pour la clé.

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

J'utilise cette façon résoudre dans mon cas

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

Avec le code

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

}
2
HaikMnatsakanyan

Eh bien, l’Assemblée est Microsoft.VisualStudio.Modeling.Sdk.dll. fourni avec le SDK de Visual Studio (avec le package d’intégration Visual Studio).

Mais il serait utilisé à peu près de la même manière que votre attribut; il n'y a aucun moyen d'utiliser fortement les ressources de types dans les attributs simplement parce qu'elles ne sont pas constantes.

1
configurator

Je m'excuse pour le code VB.NET, mon C # est un peu rouillé ... Mais vous aurez l'idée, non?

Tout d’abord, créez une nouvelle classe: LocalizedPropertyDescriptor, qui hérite de PropertyDescriptor. Remplacez la propriété DisplayName comme ceci:

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 est le gestionnaire de ressources du fichier de ressources contenant vos traductions.

Ensuite, implémentez ICustomTypeDescriptor dans la classe avec les propriétés localisées et substituez la méthode 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

Vous pouvez maintenant utiliser l'attribut 'DisplayName` pour stocker une référence à une valeur dans un fichier de ressources ...

<DisplayName("prop_description")> _
Public Property Description() As String

prop_description est la clé du fichier de ressources.

0