DisplayName-Attribut aus Ressourcen?

160

Ich habe eine lokalisierte Anwendung und frage mich, ob es möglich ist, die DisplayNameEigenschaft für ein bestimmtes Modell aus einer Ressource festzulegen.

Ich möchte so etwas machen:

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

Aber ich kann es nicht, wie der Compiler sagt: "Ein Attributargument muss ein konstanter Ausdruck, ein Ausdruckstyp oder ein Arrayerstellungsausdruck eines Attributparametertyps sein" :(

Gibt es Problemumgehungen? Ich gebe Etiketten manuell aus, aber ich benötige diese für die Validatorausgabe!

Palantir
quelle

Antworten:

112

Wie wäre es mit einem benutzerdefinierten Attribut:

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

was so verwendet werden könnte:

public class MyModel 
{
    [Required]
    [LocalizedDisplayName("labelForName")]
    public string Name { get; set; }
}
Darin Dimitrov
quelle
Ja, ich habe heute Morgen an einer ähnlichen Lösung gearbeitet und es ist machbar. Dann habe ich diesen Beitrag gefunden, der den gleichen Ansatz hat: adamyan.blogspot.com/2010/02/…
Palantir
23
@Gunder sein Beitrag unter diesem (mit den meisten Stimmen) ist eine viel schönere Lösung. Nur für Leute, die nur die akzeptierten Beiträge lesen
321X
1
Dies funktioniert tatsächlich NICHT für den Zugriff auf verschiedene Übersetzungen, da für alle Benutzer derselbe Wert zurückgegeben wird, egal was passiert. Speichern Sie die Ressourcen-ID in einer lokalen Variablen und überschreiben Sie stattdessen DisplayName
Fischer
5
Vorschlag für TODO: return Resources.Language.ResourceManager.GetString (resourceId);
Leonel Sanches da Silva
4
Sie sollten verwenden: [Anzeige (Name = "labelForName", ResourceType = typeof (Resources.Resources))] wie unten beschrieben ...
Frank Boucher
375

Wenn Sie MVC 3 und .NET 4 verwenden, können Sie das neue DisplayAttribut im System.ComponentModel.DataAnnotationsNamespace verwenden. Dieses Attribut ersetzt das DisplayNameAttribut und bietet viel mehr Funktionen, einschließlich Lokalisierungsunterstützung.

In Ihrem Fall würden Sie es folgendermaßen verwenden:

public class MyModel
{
    [Required]
    [Display(Name = "labelForName", ResourceType = typeof(Resources.Resources))]
    public string name{ get; set; }
}

Nebenbei bemerkt, dieses Attribut funktioniert nicht mit Ressourcen innerhalb App_GlobalResourcesoder App_LocalResources. Dies hat mit dem benutzerdefinierten Tool ( GlobalResourceProxyGenerator) zu tun, das diese Ressourcen verwenden. Stellen Sie stattdessen sicher, dass Ihre Ressourcendatei auf "Eingebettete Ressource" eingestellt ist, und verwenden Sie das benutzerdefinierte Tool "ResXFileCodeGenerator".

(Als weitere Randnotiz sollten Sie MVC nicht verwenden App_GlobalResourcesoder verwenden App_LocalResources. Weitere Informationen dazu, warum dies hier der Fall ist, finden Sie hier. )

René
quelle
Dies ist gut für das hier verwendete Beispiel, funktioniert jedoch nicht für die meisten dynamischen Eigenschaftssetter, die Zeichenfolgen benötigen.
Kingdango
1
Dies ist gut, wenn wir unser gesamtes Gebietsschema vor der Bereitstellung für die Produktion bei uns haben. Aber wenn ich db für db-Strings aufrufen möchte, ist dieser Ansatz nicht richtig. Der Nachteil der Ressourcendatei besteht auch darin, dass sie erneut kompiliert werden muss, um die Änderungen zu bewirken. Dies bedeutet, dass Sie eine andere Version für den Client freigeben müssen. Ich sage dies nicht als einen schlechten Ansatz, bitte fühlen Sie sich nicht so
kbvishnu
Nur ein Problem: Wir müssen den Ressourcentyp im Modell kennen. Ich habe eine Modell-DLL und eine Website in zwei verschiedenen Projekten. Ich möchte in der Lage sein, Anzeigenamen im Modell und Ressourcentyp in der Website
festzulegen
1
Holen Sie sich Resharper und erstellen Sie die Ressourcendatei in Ihrem Projekt. Sie wird automatisch daran erinnert und hilft Ihnen dann, den Namen in die Ressourcendatei zu verschieben.
anIBMer
14
Mit C # 6 können Sie stattdessen Name = "labelForName"auch verwenden Name = nameof(Resources.Resources.labelForName).
Uwe Keim
35

Wenn Sie Ihre Ressourcendatei öffnen und den Zugriffsmodifikator in "öffentlich" oder "intern" ändern, wird aus Ihrer Ressourcendatei eine Klasse generiert, mit der Sie stark typisierte Ressourcenreferenzen erstellen können.

Option zur Generierung von Ressourcendateicode

Das heißt, Sie können stattdessen so etwas tun (mit C # 6.0). Dann müssen Sie sich nicht mehr daran erinnern, ob der Vorname in Kleinbuchstaben oder Kamel geschrieben war. Und Sie können sehen, ob andere Eigenschaften denselben Ressourcenwert verwenden, indem Sie alle Referenzen finden.

[Display(Name = nameof(PropertyNames.FirstName), ResourceType = typeof(PropertyNames))]
public string FirstName { get; set; }
Tikall
quelle
Funktioniert das mit Winforms? Ich habe eine Klasse, die ich hinzugefügt habe. Annotation von Ressourcen anzeigen und die GetAttributeFrom-Methode von link verwendet , um den Namen der Eigenschaft abzurufen, aber die lokalisierte zeigt sie nicht an!
Dark_Knight
2
Verwenden Sie attribute.Name oder attribute.GetName (), um den lokalisierten Text abzurufen? In der Dokumentation zu .Name heißt es: "Verwenden Sie diese Eigenschaft nicht, um den Wert der Name-Eigenschaft abzurufen. Verwenden Sie stattdessen die GetName-Methode." msdn.microsoft.com/en-us/library/…
Tikall
Ja, ich habe herausgefunden, dass ich GetName () verwenden musste. Danke
Dark_Knight
20

Aktualisieren:

Ich weiß, dass es zu spät ist, aber ich möchte dieses Update hinzufügen:

Ich verwende den konventionellen Modell-Metadaten-Anbieter, der von Phil Haacked vorgestellt wurde. Er ist leistungsfähiger und einfacher anzuwenden. Sehen Sie sich das an: ConventionalModelMetadataProvider


Alte Antwort

Hier, wenn Sie viele Arten von Ressourcen unterstützen möchten:

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

Dann benutze es so:

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

Das vollständige Tutorial zur Lokalisierung finden Sie auf dieser Seite .

Wahid Bitar
quelle
1
+1. Haacks Lösung ist definitiv die eleganteste im Vergleich zu den anderen hier. Es passt sehr gut in den konventionellen Programmierstil in ASP.NET MVC und kann einfach über einen einzelnen Nuget-Befehl und eine einzelne Codezeile in Global.asax.cs implementiert werden.
Nilzor
11

Ich habe die Antwort von Gunders erhalten, die mit meinen App_GlobalResources arbeitet, indem ich die Ressourceneigenschaften ausgewählt und "Benutzerdefiniertes Tool" auf "PublicResXFileCodeGenerator" umgeschaltet und die Aktion auf "Eingebettete Ressource" erstellt habe. Bitte beachten Sie den Kommentar von Gunders unten.

Geben Sie hier die Bildbeschreibung ein

Klappt wunderbar :)

Magnus Karlsson
quelle
Dies führt zu einer Ressourcendatei, die so kompiliert wird, als hätten Sie eine Ressourcendatei außerhalb von App_GlobalResources hinzugefügt. Ihre Ressourcendatei verhält sich also nicht mehr wie eine Ressourcendatei "App_GlobalResources", was absolut in Ordnung ist. Aber du solltest dir dessen nur bewusst sein. Sie haben also nicht mehr die "Vorteile", die Ressourcendatei in App_GlobalResources abzulegen. Du hättest es genauso gut woanders hinstellen können.
René
5
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. Definieren Sie ResourceType des Attributs, damit es nach einer Ressource sucht
  2. Definieren Sie den Namen des Attributs, das für den Schlüssel der Ressource verwendet wird. Nach C # 6.0 können Sie eine nameofstark typisierte Unterstützung verwenden, anstatt den Schlüssel fest zu codieren.

  3. Stellen Sie die Kultur des aktuellen Threads in der Steuerung ein.

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

  1. Stellen Sie die Zugänglichkeit der Ressource für öffentlich ein

  2. Zeigen Sie das Etikett wie folgt in cshtml an

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

code4j
quelle