Atrybut DisplayName z zasobów?

160

Mam zlokalizowaną aplikację i zastanawiam się, czy możliwe jest DisplayNameustawienie właściwości dla określonego modelu z zasobu.

Chciałbym zrobić coś takiego:

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

Ale nie mogę, jak mówi kompilator: "Argument atrybutu musi być wyrażeniem stałym, wyrażeniem typu lub wyrażeniem tworzenia tablicy o typie parametru atrybutu" :(

Czy są jakieś obejścia? Wyprowadzam etykiety ręcznie, ale potrzebuję ich do wyjścia z walidatora!

Palantir
źródło

Odpowiedzi:

112

Co powiesz na napisanie atrybutu niestandardowego:

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

które można wykorzystać w ten sposób:

public class MyModel 
{
    [Required]
    [LocalizedDisplayName("labelForName")]
    public string Name { get; set; }
}
Darin Dimitrov
źródło
Tak, dziś rano pracowałem nad podobnym rozwiązaniem i jest to wykonalne. Potem znalazłem ten post, który ma to samo podejście: adamyan.blogspot.com/2010/02/…
Palantir
23
@Gunder jego post, poniżej tego (z największą liczbą głosów), jest o wiele ładniejszym rozwiązaniem. Tylko dla osób, które czytają tylko zaakceptowane posty
321X
1
To faktycznie NIE działa w przypadku dostępu do różnych tłumaczeń, ponieważ zwróci tę samą wartość dla wszystkich użytkowników bez względu na wszystko. Przechowuj identyfikator zasobu w zmiennej lokalnej i zamiast tego zastąp DisplayName
Fischer,
5
Sugestia dotycząca TODO: zwraca Resources.Language.ResourceManager.GetString (resourceId);
Leonel Sanches da Silva,
4
Należy użyć: [Display (Name = "labelForName", ResourceType = typeof (Resources.Resources))], jak opisano poniżej ...
Frank Boucher
375

Jeśli używasz MVC 3 i .NET 4, możesz użyć nowego Displayatrybutu w System.ComponentModel.DataAnnotationsprzestrzeni nazw. Ten atrybut zastępuje DisplayNameatrybut i zapewnia znacznie więcej funkcji, w tym obsługę lokalizacji.

W twoim przypadku użyłbyś tego w ten sposób:

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

Na marginesie, ten atrybut nie będzie działał z zasobami wewnątrz App_GlobalResourceslub App_LocalResources. Ma to związek z narzędziem niestandardowym ( GlobalResourceProxyGenerator) używanym przez te zasoby. Zamiast tego upewnij się, że plik zasobów jest ustawiony na „Zasób osadzony” i użyj niestandardowego narzędzia „ResXFileCodeGenerator”.

(Na marginesie, nie powinieneś używać App_GlobalResourcesani App_LocalResourcesz MVC. Możesz przeczytać więcej o tym, dlaczego tak się dzieje tutaj )

René
źródło
Jest to dobre dla konkretnego przykładu użytego tutaj, ale nie będzie działać dla większości dynamicznych metod ustawiających właściwości, które chcą ciągów.
kingdango
1
Jest to dobre, gdy przed wdrożeniem do produkcji mamy ze sobą wszystkie nasze lokalizacje. Ale jeśli chcę wywołać db dla ciągów db, to tego rodzaju podejście nie jest właściwe. Wadą pliku zasobów jest również to, że wymaga on ponownej kompilacji, aby wprowadzić zmiany, co oznacza, że ​​musisz udostępnić klientowi kolejną wersję. Nie mówię tego jako złego podejścia, proszę nie czuj się tak
kbvishnu
Tylko problem: musimy znać typ zasobu w modelu. Mam modelową bibliotekę DLL i witrynę internetową w dwóch różnych projektach. Chciałbym móc ustawić nazwy wyświetlane w modelu i ustawić typ zasobu w witrynie internetowej ...
Kek
1
Pobierz Resharper i utwórz plik zasobów w swoim projekcie, a on automatycznie przypomni, a następnie pomoże Ci przenieść Nazwę do pliku zasobów.
anIBMer
14
W C # 6 zamiast Name = "labelForName"możesz również użyć Name = nameof(Resources.Resources.labelForName).
Uwe Keim
35

Jeśli otworzysz plik zasobów i zmienisz modyfikator dostępu na publiczny lub wewnętrzny, wygeneruje on klasę z pliku zasobów, która umożliwi tworzenie silnie wpisanych odwołań do zasobów.

Opcja generowania kodu pliku zasobów

Co oznacza, że ​​zamiast tego możesz zrobić coś takiego (używając C # 6.0). Wtedy nie musisz pamiętać, czy imię było zapisane małymi literami, czy wielbłądami. Możesz sprawdzić, czy inne właściwości używają tej samej wartości zasobu za pomocą polecenia Znajdź wszystkie odwołania.

[Display(Name = nameof(PropertyNames.FirstName), ResourceType = typeof(PropertyNames))]
public string FirstName { get; set; }
Tikall
źródło
czy to zadziała z Winforms? Mam klasę, którą dodałem Wyświetl adnotację z zasobów i użyłem metody GetAttributeFrom z linku, aby uzyskać nazwę właściwości, ale nie pokazuje zlokalizowanej!
Dark_Knight,
2
Czy używasz attribute.Name lub attribute.GetName (), aby uzyskać zlokalizowany tekst? Dokumentacja dla .Name mówi: „Nie używaj tej właściwości do pobierania wartości właściwości Name. Zamiast tego użyj metody GetName”. msdn.microsoft.com/en-us/library/…
Tikall
Tak, zorientowałem się, że muszę użyć GetName (). Dzięki
Dark_Knight,
20

Aktualizacja:

Wiem, że jest za późno, ale chciałbym dodać tę aktualizację:

Używam dostawcy metadanych modelu konwencjonalnego, który przedstawiony przez Phila Haackeda jest bardziej wydajny i łatwy w zastosowaniu, spójrz na to: ConventionalModelMetadataProvider


Stara odpowiedź

Tutaj, jeśli chcesz wspierać wiele rodzajów zasobów:

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

Następnie użyj tego w ten sposób:

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

Pełny samouczek dotyczący lokalizacji znajduje się na tej stronie .

Wahid Bitar
źródło
1
+1. Rozwiązanie Haacka jest zdecydowanie najbardziej eleganckie w porównaniu z innymi tutaj. Bardzo dobrze pasuje do stylu programowania opartego na konwencji w ASP.NET MVC i można go łatwo zaimplementować za pomocą pojedynczego polecenia nuget i jednego wiersza kodu w Global.asax.cs.
Nilzor
11

Otrzymałem odpowiedź Gunders podczas pracy z moimi zasobami App_GlobalResources, wybierając właściwości zasobów i przełączając „Narzędzie niestandardowe” na „PublicResXFileCodeGenerator” i budując akcję na „Zasoby osadzone”. Proszę zwrócić uwagę na komentarz Gundersa poniżej.

wprowadź opis obrazu tutaj

Działa jak marzenie :)

Magnus Karlsson
źródło
Powoduje to, że plik zasobów zostanie skompilowany tak, jakby został dodany plik zasobów poza App_GlobalResources. Dlatego plik zasobów nie będzie już zachowywał się jak plik zasobów „App_GlobalResources”, co jest absolutnie w porządku. Ale powinieneś być tego świadomy. Więc nie masz już „korzyści” z umieszczania pliku zasobów w App_GlobalResources. Równie dobrze mogłeś umieścić to gdzie indziej.
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. Zdefiniuj ResourceType atrybutu, aby szukał zasobu
  2. Zdefiniuj nazwę atrybutu, który jest używany dla klucza zasobu, po C # 6,0 można użyć nameofdo silnej obsługi typów zamiast sztywnego kodowania klucza.

  3. Ustaw kulturę bieżącego wątku w kontrolerze.

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

  1. Ustaw dostępność zasobu jako publiczny

  2. Wyświetl etykietę w cshtml w ten sposób

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

code4j
źródło