Es gibt Daten für die es keinen Sinn macht sie in einer Datenbank zu hinterlegen. Etwa, wenn sie sich nur sehr selten ändern, oder wenn eine Änderung zu einer Inkonsitenz des gesamten Systems führen kann. Die Sprache einer (Web-) Anwendung fällt mitunter in diese Kategorie, natürlich abhängig davon, ob Benutzer Sprachen bearbeiten oder hinzufügen können usw.
Im konkreten Fall will ich die Sprachen statisch verwalten, weshalb ich in meiner Domäne ein Enum Language eingeführt habe:
1: namespace Blubr.Domain.Model
2: {
3: public enum Language
4: {
5: German = 1,
6: English = 2,
7: French = 3
8: }
9: }
Wenn sich ein Benutzer registriert oder sein Profil ändert, soll er natürlich beliebig zwischen den Sprachen hin- und herschalten können, also muss man ihm die verfügbaren Sprachen anzeigen, z.B. in einer Selectbox.
Da fragt sich nur wie? Wie bekommt man nun die Werte aus der statischen Liste in eine DropDownList? Und vor allem noch in der richtigen Sprache? Hätte man die Werte in einer Datenbank, wäre das alles kein großes Problem.
Lösungen gibt es viele, von der statischen im View angelegten HTML-Select-Liste angefangen, bis zu Schleifen-Konstrukten, die für jeden Value des Enum noch in einem Wörterbuch nachschlagen und so z.B. "German" durch "Deutsch" ersetzen.
Da das ASP.NET-MVC-Team aber mit Version 2.0 des MVC-Frameworks die Verwendung von DataAnnotations einführt, wähle ich diesen Weg (auch weil er mir sehr gut gefällt). Im nächten Schritt erzeuge ich also ein View-Model und deklariere die Sprache als Attribut:
1: namespace Blubr.Web.Models
2: {
3: public enum LanguageViewModel
4: {
5: [Display(Name = "Deutsch")]
6: German = 1,
7: [Display(Name = "English")]
8: English = 2,
9: [Display(Name = "Française")]
10: French = 3
11: }
12: }
Jetzt habe ich also schon einmal die korrekten Übersetzungen drin. Damit diese aus dem Objekt automatisch als Listen-Elemente erzeugt werden können, war etwas Handarbeit notwendig, im Ergebnis in Form einer Extension-Method:
1: public static SelectList ToSelectList<T>(this T enumeration)
2: {
3:
4: var source = Enum.GetValues(typeof(T));
5: var items = new Dictionary<object, string>();
6: var displayAttributeType = typeof (DisplayAttribute);
7:
8: foreach (var value in source)
9: {
10: FieldInfo field = value.GetType().GetField(value.ToString());
11: DisplayAttribute attrs = (DisplayAttribute)field.
12: GetCustomAttributes(displayAttributeType, false).First();
13: items.Add(value, attrs.GetName());
14: }
15:
16: return new SelectList(items, "Key", "Value", enumeration);
17:
18: }
Diese sieht nur auf den ersten Blick kompliziert aus. Im Prinzip schnappt sie sich per Reflection die Eigenschaften der einzelnen Werte der Aufzählung und erzeugt daraus eine Liste von Parametern für die DropDownList im View:
1: namespace Blubr.Web.Models
2: {
3: public class SignUpViewModel
4: {
5: public LanguageViewModel Language { get; set; }
6: }
7: }
1: <%= Html.LabelFor(m => m.Language) %>
2: <%= Html.DropDownListFor(m => m.Language, Model.Language.ToSelectList()) %>
Natürlich lässt sich das Ganze auch lokalisieren. Hierzu legt man wie gewohnt eine Ressourcen-Datei an und gibt dann bei der Deklaration des Attributes den Parameter ResourceType an, dem der Typ der Ressource zugewiesen wird. Der Rest bleibt gleich:
1: public enum LanguageViewModel
2: {
3: [Display(Name = "Deutsch")]
4: German = 1,
5: [Display(Name = "English")]
6: English = 2,
7: [Display(ResourceType = typeof(Localization.Demo), Name = "French")]
8: French = 3
9: }
Im Ergebnis wird automatisch der richtige String entsprechend der aktuellen Culture aus der Ressource-Datei gezogen, der Parameter "Name" fungiert dabei in diesem Fall als ID.
Damit das fehlerfrei funktioniert muss man übrigens aufpassen, wie man in der Extension-Method auf die Name-Eigenschaft zugreift. Den Hinweis habe ich nur zufällig in der MSDN gefunden: ein direkter Zugriff auf die Name-Property funktioniert im Fall der Lokalisierung nämlich nicht (zurückgegeben wird ein leerer String), erst der Gang über die GetName()-Property bringt das gewünschte Ergebnis.