127 votes

Localisation de DisplayNameAttribute

Je cherche un moyen de localiser les noms des propriétés affichés dans un PropertyGrid. Le nom de la propriété peut être "remplacé" en utilisant 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)]  // ne compile pas
   string MyProperty {get; set;}
}

J'ai cherché un peu partout et trouvé des suggestions pour hériter de DisplayNameAttribute afin de pouvoir utiliser des ressources. Je me retrouverais avec du code comme suit:

class Foo
{
   [MyLocalizedDisplayAttribute("MyPropertyNameLocalized")] // non fortement typé
   string MyProperty {get; set;}
}

Cependant, je perdrais les avantages des ressources fortement typées, ce qui n'est certainement pas une bonne chose. Ensuite, j'ai découvert DisplayNameResourceAttribute qui pourrait être ce que je recherche. Mais il est censé être dans l'espace de noms Microsoft.VisualStudio.Modeling.Design et je ne trouve pas quelle référence je suis censé ajouter pour cet espace de noms.

Est-ce que quelqu'un sait s'il y a un moyen plus facile d'atteindre la localisation de DisplayName de manière correcte ? ou s'il y a un moyen d'utiliser ce que Microsoft semble utiliser pour Visual Studio ?

2 votes

Que diriez-vous de Display(ResourceType=typeof(ResourceStrings),Name="MyProperty‌​y") voir msdn.microsoft.com/en-us/library/…

0 votes

@Peter lit attentivement le post, il veut exactement le contraire, en utilisant des ResourceStrings et des vérifications à la compilation, et non des chaînes codées en dur...

120voto

RandomEngy Points 6937

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

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

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

0 votes

J'ai cherché partout avant de trouver cette page... c'est à la vie sauve. Merci! Ça fonctionne bien sur MVC5 pour moi.

1 votes

Si le compilateur se plaint de typeof(MyResources), vous devrez peut-être définir le modificateur d'accès de votre fichier de ressources sur Public.

80voto

Jeff Yates Points 36725

Nous faisons cela pour plusieurs 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 passent un nom de ressource plutôt que la chaîne actuelle. Le nom de la ressource est ensuite utilisé pour effectuer une recherche dans les ressources DLL afin de renvoyer la chaîne réelle.

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 lors de l'utilisation de l'attribut et spécifier vos noms de ressource comme des constantes dans une classe statique. De cette manière, vous obtenez des déclarations comme.

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

Mise à jour
ResourceStrings ressemblerait à (notez que chaque chaîne ferait référence au nom d'une ressource spécifiant la chaîne réelle):

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

0 votes

Lorsque j'essaie cette approche, je reçois un message d'erreur disant "Un argument d'attribut doit être une expression constante, une expression typeof ou une expression de création de tableau d'un type de paramètre d'attribut". Cependant, passer la valeur à LocalizedDisplayName en tant que chaîne de caractères fonctionne. J'aimerais que ce soit fortement typé.

1 votes

@Andy : Les valeurs dans ResourceStrings doivent être des constantes, comme indiqué dans la réponse, et non des propriétés ou des valeurs en lecture seule. Elles doivent être marquées comme const et faire référence aux noms des ressources, sinon vous obtiendrez une erreur.

1 votes

J'ai répondu à ma propre question, il s'agissait de l'endroit où vous aviez les Resources.ResourceManager, dans mon cas les fichiers resx sont des fichiers resx publics générés donc c'était [MonEspaceDeNoms].[MonFichierRessource].ResourceManager.GetString("Mon‌​Texte");

42voto

PowerKiKi Points 1259

Voici la solution avec laquelle j'ai fini par aboutir dans une assembly séparée (appelée "Common" 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 assez laid car j'utilise des chaînes littérales pour la clé de ressource. Utiliser une constante signifierait de modifier Resources.Designer.cs ce qui n'est probablement pas une bonne idée.

En conclusion : Je ne suis pas satisfait de cela, mais je le suis encore moins de Microsoft qui ne peut pas fournir quelque chose d'utile pour une telle tâche commune.

0 votes

Très utile. Merci. À l'avenir, j'espère que Microsoft proposera une belle solution qui offre un moyen de référencer les ressources de manière fortement typée.

0 votes

Ya cette chaîne de caractères est vraiment nulle :( Si vous pouviez obtenir le nom de la propriété de la propriété qui utilise l'attribut, vous pourriez le faire à la manière de la convention sur la configuration, mais cela ne semble pas possible. Prendre soin des énumérations "fortement typées" que vous pourriez utiliser n'est pas vraiment maintenable :/

0 votes

C'est une bonne solution. Je ne parcourrais tout simplement pas la collection des propriétés de ResourceManager. Au lieu de cela, vous pouvez simplement obtenir la propriété directement du type fourni dans le paramètre : PropertyInfo property = resourceManagerProvider.GetProperty(resourceKey, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);

14voto

zielu1 Points 530

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

<#@ 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
{
 /// 
 /// Attribut de nom d'affichage localisé
 /// 
 public class LocalizedDisplayNameAttribute : DisplayNameAttribute
 {
  readonly string _resourceName;

  /// 
  /// Initialise une nouvelle instance de la classe .
  /// 
  /// Nom de la ressource.
  public LocalizedDisplayNameAttribute(string resourceName)
   : base()
  {
   _resourceName = resourceName;
  }

  /// 
  /// Obtient le nom d'affichage d'une propriété, d'un événement, ou d'une méthode publique sans argument stockée dans cet attribut.
  /// 
  /// 
  /// 
  /// Le nom d'affichage.
  /// 
  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#>";
  <# } #>
  }
 }
}

0 votes

Quel serait le rendu final ?

9voto

YYFish Points 126

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

Tout d'abord, un modèle T4 est nécessaire pour générer des constantes afin d'éviter les chaînes désagréables. Nous avons un fichier de ressources 'Labels.resx' qui contient toutes les chaînes d'étiquettes. 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();
  var manager = Resources.Labels.ResourceManager;

  IDictionaryEnumerator enumerator = manager.GetResourceSet(CultureInfo.CurrentCulture,  true, true)
                                             .GetEnumerator();
  while (enumerator.MoveNext())
  {
        resourceStrings.Add(enumerator.Key.ToString());
  }
#>     

// Ce fichier est généré automatiquement. NE MODIFIEZ PAS le contenu à l'intérieur.

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' afin de lire automatiquement dans '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 modifier ces attributs de validation par défaut. J'utilise l'attribut 'Required' 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 dans 'Label.resx', mais si vous le définissez via 'DisplayLabel', il l'utilisera à la place.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X