Glengamoi (Forum) · AspHeute · .NET Heute (RSS-Suche) · AspxFiles (Wiki) · .NET Blogs
ASP German Homepage Homepage
 

Liste

.NET 2.0 (1)
.NET Allgemein (16)
.NET Fu (5)
ADO.NET (11)
Aprilscherz (3)
ASP Grundlagen (44)
ASP Tricks (83)
ASP.NET (44)
ASPIntranet.de (5)
C# (28)
Datenbank (44)
Dokumentation (4)
IIS 6.0 (1)
Komponenten (29)
Optimierung (10)
Server (21)
Sicherheit (34)
Tee Off (6)
VB.NET (6)
WAP (8)
Web Services (11)
XML (9)

RSS 2.0 - Die neuesten fünf Artikel auf AspHeute.com


 

Suchen





 

English Articles
Chinese Articles
Unsere Autoren
 
Link zu AspHeute
Impressum
Werben
Anfragen

Mehrsprachige Applikationen in .Net

Geschrieben von: Dani Meier
Kategorie: .NET Allgemein

Sicher sind die meisten schon einmal vor der Aufgabe gestanden, eine Applikation mehrsprachig umzusetzen. Mögliche Lösungsansätze gibt es dafür mehr als genug:

  • Ressource-Files
  • Xml-Files
  • Flat-Files
  • Datenbanken
  • Usw.

Datenbanken haben den Nachteil, dass man seine Strings nicht mal einfach so dem Übersetzer geben kann. Und Flat-Files sind wie der Name schon sagt, einfach zu flach - da mit normalem Aufwand keine strukturierte Ablage (und vor allem Abfrage) der Strings möglich ist, verliert man schnell den Überblick. Ressource-Files (.resx) liegen sehr nahe, da der Zugriff auf diese bereits vom Framework bereitgestellt wird, und da Ressource-Files XML-Files sind, ist auch der Umgang mit ihnen kein Problem; es gibt aber durchaus Situationen, wo "normale" XML-Files besser am Platz wären - z.B. wenn Plattform-Unabhängigkeit eine Rolle spielt. Wie eine Lösung damit aussehen könnte, wird in diesem Artikel beschrieben.

Die Idee

Die Anforderungen sind folgende:

  • 1-n Sprachen werden unterstützt (muss soweit skalierbar sein, dass neue Sprachen jederzeit hinzugefügt werden können, ohne dass die Applikation angepasst werden muss).
  • Die Strings müssen in verschiedene Bereiche unterteilt werden können - sonst verliert man schnell den Überblick über die verwalteten Strings
  • Das Source-File sollte "Notepad-kompatibel" sein

Die Lösung

Wenn Ressource-Files wegfallen, liegen "normale" XML-Files nahe. Alle Strings werden in ein zentrales XML-File geladen. Das XML-File wird eingelesen und entsprechend gecached - mittels einer CacheDependency auf dem XML-File wird sichergestellt, dass bei jeder Aktualisierung des Files der Cache ebenfalls erneuert wird - so läuft man auch bei sehr vielen Strings nicht Gefahr, Performance-Probleme zu erhalten. Der Zugriff auf die Strings funktioniert über eine zentrale Klasse, die die ganze Zugriffs- und Caching-Logik kapseln soll.

Datenmodell

Die XML-Datenstruktur sieht exemplarisch wie folgt aus:

<StringPool>
<Region name="Administration">
    <Text key="CommitButton">
      <D>Bestätigen</D>
      <F>Confirmer</F>
    </Text >
    <Text key="DeleteButton">
      <D>Löschen</D>
      <F>Supprimer</F>
    </ Text>
</Region>
<Region name="Gallery">
    <Text key="PictureInit">
      <D>Bild</D>
      <F>Image</F>
    </Text>
</Region>
</StringPool>

Die Unterteilung in die Region-Nodes ermöglicht es, eine gewisse Struktur in das XML-File zu bringen - so behält man auch bei hunderten von Texten den Überblick. Die Sprachen können frei ergänzt werden - die Bezeichnung des Sprach-Nodes (hier D, F) ist dabei egal, da dessen Bezeichnung dann einfach als Parameter mitgegeben wird (dazu später mehr). Und da es sich hierbei augenscheinlich um ein ganz normales XML-File handelt, kann dieses natürlich auch einfach mit Notepad bearbeitet werden.

Als Datenspeicher für die Daten bieten sich durch das Key / Value - Verfahren Dictionary-Objekte an. Jede Hierarchie-Stufe (Region, Text, [Sprache]) wird in ein eigenes Dictionary-Objekt gespeichert - und diese werden dann entsprechend verschachtelt.

Für die beiden Stufen Regionen und Text werden HashTables verwendet - für die Texte ein StringDictionary; da auf dieser Ebene nur Strings abgelegt werden, bietet sich dieses durch seine verbesserte Performance gegenüber einer HashTable an.

Das Datenmodell der StringPool-Klasse:

Real-Life

So weit die Theorie - aber wie programmiert man dies nun in C#?

Die Klasse StringPool - die das ganze heavy lifting für uns machen wird - hat folgende Signatur:

public class StringPool
{
  public void LoadStrings()

  private XmlDocument LoadSourceFile(string file)

  public string GetString(string region, string key, string language)

  public StringPool GetInstance()
}

Vier einfache Methoden reichen also bereits:

  • LoadStrings Lädt die Strings aus dem XML-File in das beschriebene Datenmodell
  • LoadSourceFile Wird nur dazu verwendet, das XML-File in eine XmlDocument-Instanz zu laden, die dann anschliessend in der LoadStrings-Methode verarbeitet wird.
  • GetString Über diese Methode können wir auf unsere Strings zugreifen. Als Parameter wird der Region-Name, der Name des Text-Keys sowie natürlich die gewünschte Sprache übergeben; wenn eine ungültige Sprache übergeben wird, verwendet die Methode die definierte Default-Sprache.
  • GetInstance Diese Methode liefert uns ein Objekt der StringPool-Klasse über das wir dann unsere Strings beziehen können. Dabei achtet die Methode darauf, dass erstens immer nur eine Instanz von StringPool besteht (Singleton-Pattern) und zweitens diese Instanz jeweils über das Cache[]-Objekt bezogen wird und nicht etwa jedes mal neu erstellt werden muss (siehe nächster Abschnitt).

Single-Leben

Nun wäre es natürlich nicht sehr klug, bei jedem String-Request das ganze XML-File in die Datenstruktur zu laden, nur um danach einen einzigen String zu verwenden und dann das ganze Objekt wieder zu "zerstampfen"…

Ziel muss es also sein, die StringPool-Instanz anfangs einmal zu erstellen, und dann für alle User über ein geschicktes Caching-Verfahren möglichst performant verfügbar zu machen - wobei es natürlich keinen Sinn macht, für jeden User ein eigenes StringPool-Objekt zu führen.

Mit einem Singleton-Pattern können wir genau das sicherstellen - nämlich dass zu jedem Zeitpunkt immer nur genau ein StringPool-Objekt besteht - egal wieviele User die Applikation gerade nutzen. Diese eine Instanz wird dann gecached, und jeweils neu erstellt, wenn Änderungen am XML-File gemacht wurden.

Für WebApplikationen bietet sich hier natürlich das Cache[]-Objekt an - aber was ist mit WinForms? Das Cache[]-Objekt befindet sich im Namespace System.Web.Caching - hört sich also nicht passend für eine WinForm-Anwendung an - ist es aber absolut! Über die Klasse System.Web.HttpRuntime kann man für WinForm- (oder natürlich auch andere) Applikationen ein Cache[]-Objekt erstellen.

if(HttpContext.Current == null)
  myCache = HttpRuntime.Cache;
else
  myCache = HttpContext.Current.Cache;

In ersterem Fall (betrifft alle nicht ASP.NET-Anwendungen) beziehen wir unser Cache-Objekt also über die HttpRuntime-Klasse, im zweiten (ASP.NET-Anwendungen) über den aktuellen HttpContext.

Jetzt muss die Methode für uns noch prüfen, ob im Cache bereits eine Instanz von StringPool besteht - wenn nicht wird diese erstellt, die Strings geladen und dann in den Cache gespeichert.

if(myCache[CACHE_KEY] == null) {
  // Erstelle StringPool-Instanz und lade Strings
  StringPool sp = new StringPool();
  sp.LoadStrings();
				
  // Cache neu füllen
  myCache.Insert(CACHE_KEY, sp, new CacheDependency(SourceFile));
}

Damit die Klasse nicht anderweitig instanziert werden kann (also unseren Singleton umgehen), definieren wir einen privaten Konstruktor.

Praxis

Nun ist unsere Klasse schon bereit, genutzt zu werden; wir können unsere Strings über die GetString-Methode abfragen - ohne jemals Gefahr zu laufen, dass wir auf ein nicht instanziertes Objekt zugreifen und somit eine NullRefereceException werfen:

String myString = StringPool.GetInstance().GetString("Administration", "CommitButton", "D");

Sollte die übergebene Sprache (hier: "D") in unserer Datenquelle nicht vorhanden sein, wird eine im .config-File definierte Default-Sprache verwendet. Ein Aufruf ohne den Sprachparameter funktioniert dank einer Überladung der GetString()-Methode ebenfalls - auch dort wird dann jeweils die definierte Default-Sprache verwendet.

String myString = StringPool.GetInstance().GetString("Administration", "CommitButton");

Schlußbemerkung

Dieser Artikel soll einen einfachen, aber dennoch praktikablen Ansatz bieten, mehrsprachige Applikationen in .Net zu entwickeln. Vorteile dieses Ansatzes liegen sicherlich in der einfachen Handhabung des XML-Files - es werden keine speziellen Programme oder User-Interfaces benötigt, um die Strings anzupassen - und in der Plattformunabhängigkeit. Das hier verwendete Konzept kann ohne Probleme auch auf andere Datenquellen (z.B. eine Datenbank) angewendet werden - eine Anpassung der LoadStrings()-Methode genügt.

Download des Codes

Klicken Sie hier, um den Download zu starten.

Wenn Sie jetzt Fragen haben...

Wenn Sie Fragen rund um die in diesem Artikel vorgestellte Technologie haben, dann schauen Sie einfach bei uns in den Community Foren der deutschen .NET Community vorbei. Die Teilnehmer helfen Ihnen gerne, wenn Sie sich zur im Artikel vorgestellten Technologie weiterbilden möchten.

Eine weitere sehr hilfreiche Resource ist das deutsche ASP.NET Wiki, das als zentrale Anlaufstelle für Tips, Tricks, Know How und alles Nützliche was man in seinem Alltag als (ASP).NET-Entwickler so braucht und entdeckt gedacht ist.

Haben Sie Fragen die sich direkt auf den Inhalt des Artikels beziehen, dann schreiben Sie dem Autor! Unsere Autoren freuen sich über Feedback zu ihren Artikeln. Ein einfacher Klick auf die Autor kontaktieren Schaltfläche (weiter unten) und schon haben Sie ein für diesen Artikel personalisiertes Anfrageformular.

 

Und zu guter Letzt möchten wir Sie bitten, den Artikel zu bewerten. Damit helfen Sie uns, die Qualität der Artikel zu verbessern - und anderen Lesern bei der Auswahl der Artikel, die sie lesen sollten.

Bewerten Sie diesen Artikel
 Sehr gut   Nicht genügend  
   1  2  3  4  5  
 

  
   Für Ausdruck optimierte Seite

©2000-2006 AspHeute.com
Alle Rechte vorbehalten. Der Inhalt dieser Seiten ist urheberrechtlich geschützt.
Eine Übernahme von Texten (auch nur auszugsweise) oder Graphiken bedarf unserer schriftlichen Zustimmung.