Dieser Artikel wurde maschinell übersetzt.

Silverlight-Lokalisierung

Tipps und Tricks für das Laden von Silverlight-Locale-Ressourcen, Teil 2

Matthew Delisle

Das Codebeispiel herunterladen

Im ersten Artikel dieser Serie (msdn.microsoft.com/magazine/gg650657), ich bedeckte das Laden von Ressourcen in Silverlight verwenden einen Windows Communication Foundation (WCF)-Dienst, eine einfache Datenbank-Schema und clientseitigen code, die die Benutzeroberfläche einer Änderung Ressourcen benachrichtigt.In dieser Lösung wurden die Standardressourcen durch eine Web Service-Aufruf während der Initialisierung der Anwendung geladen.In diesem Artikel zeigen John Brodeur und ich Sie um die Standardressourcen zu laden, ohne alle Webdienstaufrufe wie.

Die Standard-Lokalisierung

Im standard Lokalisierungsprozess wie im vorherigen Artikel erläutert, es gibt ein paar Möglichkeiten, Gebietsschema Ressourcen abrufen.Eine häufig verwendete Methode ist RESX-Dateien in der Anwendung zur Entwurfszeit einbetten als gewiesen Abbildung 1.

Resource Files Embedded in the Application

Abbildung 1 Ressourcendateien eingebettet in der Anwendung

Der Nachteil einer Methode, die Ressourcen in der Anwendung eingebettet ist, dass alle Ressourcen dann mit der Anwendung heruntergeladen werden.Alle systemeigenen Lokalisierung in Silverlight verfügbaren Methoden einbetten die Ressourcen in die Anwendung in irgendeiner Weise.

Eine bessere Lösung ist, nur ein Standard-Ressourcenset einzubetten und andere Ressourcen bei Bedarf zu laden.Laden von Ressourcen auf Nachfrage kann in vielfältiger Weise erreicht werden: durch das Abrufen der XAP oder RESX-Dateien, oder, wie in Teil 1 dieser Reihe gegliederten, über Web-Dienstleistungen RESX-XML-Zeichenfolgen abrufen.Das Problem, das bleibt, dagegen ist, dass das Standardgebietsschema möglicherweise nicht primären Gebietsschema des Benutzers.Deren Gebietsschema vom Standardformat unterscheidet Benutzer haben immer Ressourcen vom Server abzurufen.

Die beste Lösung ist zum Generieren einer XAP-Datei on-Demand mit des aktuellen Benutzers Gebietsschema-spezifische Ressourcen in der XAP eingebettet.Mit dieser Lösung sind keine Web Service-Aufrufe erforderlich, um die Standardressourcen für jeden Benutzer zu laden.Ein Web Service-Aufruf ist nur erforderlich, wenn das Gebietsschema zur Laufzeit ändern.Dies ist die Lösung, die wir im übrigen in diesem Artikel erläutert wird.

Ein Custom-Lokalisierungsprozess

Die benutzerdefinierte Lokalisierung-Lösung in diesem Artikel besteht aus Client- und Server-Komponenten und baut auf das CustomLocalization-Projekt in Teil 1 erstellt.Mit der ASPX-Datei mit dem Silverlight-Objekt beschreiben wir den Prozess-Anfang.

Alle Parameter für die HttpHandler müssen durch die URL der Silverlight-Anwendung übergeben werden.Um die Browser-Kultur übergeben, wir fügen einen URL-Parameter und füllen Sie es mit der aktuellen Threadkultur:

<param name="source" value="ClientBin/CustomLocalization.xap?c=
   <%= Thread.CurrentThread.CurrentCulture.Name %>&rs=ui"/>

Wir müssen auch einen Import für den System.Threading-Namespace hinzufügen, um die Thread-Klasse verwenden:

<%@ Import Namespace="System.Threading" %>

Und wir hinzugefügt einen Parameter namens Rs, der die Ressource abrufen festlegen darstellt.

Das ist alles, die was in der ASPX-Datei benötigt hat. Das Gebietsschema des Benutzers wird in der HttpHandler übergeben, die durch diese Kultur in eine XAP-Datei angegebenen Ressourcen eingebettet wird.

Erstellen des Ereignishandlers

Wir gehen jetzt eine Datei mit dem Namen XapHandler im Stammverzeichnis des Webprojekts zu erstellen. Diese Klasse wird IHttpHandler implementieren und wir werden angeben, dass es nicht wiederverwendbaren ist. Wir werden drei Felder, die CultureInfo, HttpContext und ResourceSet Objekte unter Methoden Teilen hinzufügen. Der Code aussehen so weit wie folgt:

using System.Web;
namespace CustomLocalization.Web {
  public class XapHandler : IHttpHandler {
    private CultureInfo Culture;
    private HttpContext Context;
    private string ResourceSet;

    public bool IsReusable { get { return false; } }

    public void ProcessRequest(HttpContext context) {
      throw new System.NotImplementedException();
      } } }

In der ProcessRequest-Methode wir wollen die Kultur abzurufen und Ressource festlegen, die Kultur, überprüfen und dann erstellen eine lokalisierte .xap-Datei und die Datei an den Client übertragen. Um die Parameter abzurufen, werden wir sie über das Params-Array des Request-Objekts aufrufen:

string culture = context.Request.Params["c"];]
ResourceSet = context.Request.Params["rs"];

Um die Kultur zu überprüfen, werden wir versuchen, um ein CultureInfo-Objekt zu erstellen; Wenn der Konstruktor fehlschlägt, wird die Kultur für ungültig erklärt verwendet:

if (!string.IsNullOrEmpty(culture)) {
  try {
    Culture = new CultureInfo(culture);
  }
  catch (Exception ex) {
    // Throw an error
  } }

Dies ist ein guter Platz zum Erstellen einer Utilities-Klasse, einige häufig verwendete Funktionen für die Wiederverwendung zu halten. Wir beginnen mit einer Funktion, die eine Antwort an den Client sendet und dann schließt das Response-Objekt. Dies ist nützlich für das Senden von Fehlermeldungen. Hier ist der Code:

public static void SendResponse(HttpContext context, int statusCode,  
  string message) {
  if (context == null) return;
  context.Response.StatusCode = statusCode;
  if(!string.IsNullOrEmpty(message)) {
    context.Response.StatusDescription = message;
  }
  context.Response.End();
}

Und wir werden diese Methode verwenden, um einen Fehler zu senden, wenn eine ungültige Kultur angegeben wird:

if (!string.IsNullOrEmpty(culture)) {
  try {
    Culture = new CultureInfo(culture);
  }
  catch (Exception ex) {
    // Throw an error
    Utilities.SendResponse(Context, 500,
    "The string " + culture + " is not recognized as a valid culture.");
     return;
  } }

Nach der Überprüfung der Kultur, besteht der nächste Schritt die lokalisierte .xap-Datei erstellen und Zurückgeben des Dateipfads.

Die lokalisierte XAP-Datei erstellen

Dies ist, wo alle die Magie geschieht. Wir eine Methode namens CreateLocalizedXapFile mit einem Parameter vom Typ String zu erstellen. Der Parameter gibt den Speicherort auf dem Server der Anwendung XAP-Datei, die keine eingebettete Ressourcen enthält. Wenn die XAP-Datei ohne Ressourcen auf dem Server existiert nicht, kann nicht der Prozess fortgesetzt, so dass wir einen Fehler werfen wie folgt:

string xapWithoutResources = Context.Server.MapPath(Context.Request.Path);
if (string.IsNullOrEmpty(xapWithoutResources) || !File.Exists(xapWithoutResources))
  Utilities.SendResponse(Context, 500, "The XAP file does not exist.");
  return;
}
else {
  string localizedXapFilePath = CreateLocalizedXapFile(xapWithoutResources);
}

Vor dem Eintauchen in die CreateLocalizedXapFile-Methode, sehen wir uns die Verzeichnisstruktur dieser Lösung auf dem Webserver. Angenommen, wir haben eine Webanwendung namens Acme im Web-Stammverzeichnis. Innerhalb der die Acme werden Ordner ClientBin Verzeichnis, in dem Silverlight-Anwendungen normalerweise gespeichert werden. Dies ist in dem XAP-Dateien ohne Ressourcen befinden. Unter diesem Verzeichnis anderen Verzeichnissen nach Gebietsschemabezeichner benannt (En-US, es-MX, fr-FR und So weiter), und diese Verzeichnisse sind in dem Gebietsschema-spezifische XAP-Dateien erstellt und gespeichert werden. Abbildung 2 zeigt, wie die Verzeichnisstruktur aussehen könnte.

Directory Structure for Localized XAP Files

Abbildung 2 Verzeichnisstruktur für lokalisierte XAP-Dateien

Jetzt lassen Sie uns Tauchen Sie ein in die CreateLocalizedXapFile-Methode. Es gibt zwei wichtigsten Pfade der Ausführung in dieser Methode. Die erste ist, wenn die lokalisierte .xap-Datei vorhanden ist und auf dem neuesten Stand ist. In diesem Fall der Prozess ist trivial und alles, was passiert ist, dass der vollständige Pfad der lokalisierten .xap-Datei zurückgegeben wird. Der zweite Weg ist, wenn die lokalisierte .xap-Datei nicht existiert oder veraltet ist. Die lokalisierte .xap-Datei wird als veraltet ist älter als die einfache .xap-Datei oder die RESX-Datei, die in es eingebettet werden soll. Die einzelnen RESX-Dateien werden außerhalb der lokalisierten XAP-Datei gespeichert, so dass sie leicht geändert werden können, und diese Dateien verwendet werden, um festzustellen, ob die lokalisierte .xap-Datei aktuell ist. Wenn die lokalisierte .xap-Datei veraltet ist, wird es mit der einfachen XAP-Datei überschrieben, und die Ressourcen sind in dieser Datei injiziert. Abbildung 3 zeigt die kommentierte-Methode.

Abbildung 3 Die CreateLocalizedXapFile-Methode

private string CreateLocalizedXapFile(string filePath) {
  FileInfo plainXap = new FileInfo(filePath);
  string localizedXapFilePath = plainXap.FullName;

  try {
    // Get the localized XAP file
    FileInfo localizedXap = new FileInfo(plainXap.DirectoryName + 
      "\\" + Culture.Name + "\\" + plainXap.Name);
                
    // Get the RESX file for the locale
    FileInfo resxFile = new FileInfo(GetResourceFilePath(
      Context, ResourceSet, Culture.Name));

    // Check to see if the file already exists and is up to date
    if (!localizedXap.Exists || (localizedXap.LastWriteTime < 
      plainXap.LastWriteTime) || 
      (localizedXap.LastWriteTime < resxFile.LastWriteTime)) {
    if (!Directory.Exists(localizedXap.DirectoryName))  {
      Directory.CreateDirectory(localizedXap.DirectoryName);
     }
                    
     // Copy the XAP without resources
     localizedXap = plainXap.CopyTo(localizedXap.FullName, true);

     // Inject the resources into the plain XAP, turning it into a localized XAP
     if (!InjectResourceIntoXAP(localizedXap, resxFile)) {
       localizedXap.Delete();
  } }                
     if (File.Exists(localizedXap.FullName)) {
       localizedXapFilePath = localizedXap.FullName;
     } }
  catch (Exception ex) {
    // If any error occurs, throw back the error message
    if (!File.Exists(localizedXapFilePath)) {
      Utilities.SendResponse(Context, 500, ex.Message);
    } }
  return localizedXapFilePath;
}

Die GetResourceFilePath-Methode zeigt Abbildung 4. Die Parameter dieser Methode sind der Kontext, Ressourcensatz und Kultur. Wir erstellen eine Zeichenfolge, die die Ressourcendatei darstellt, überprüfen, wenn es vorhanden ist und, wenn ja, zurückgeben den Dateipfad.

Abbildung 4 Die GetResourceFilePath-Methode

private static string GetResourceFilePath(
  HttpContext context, string resourceSet, string culture) {
  if (context == null) return null;
  if (string.IsNullOrEmpty(culture)) return null;

  string resxFilePath = resourceSet + "." + culture + ".resx";
string folderPath = context.Server.MapPath(ResourceBasePath);
FileInfo resxFile = new FileInfo(folderPath + resxFilePath);

if (!resxFile.Exists) {
  Utilities.SendResponse(context, 500, "The resx file does not exist 
    for the locale " + culture);
}
return resxFile.FullName;
}

Injektion von Ressourcen in eine XAP-Datei

Jetzt fahren Sie fort, die InjectResourceIntoXAP-Methode. Wie die meisten Silverlight-Entwickler wissen, ist eine .xap-Datei eine .zip-Datei in Verkleidung. Erstellen eine .xap-Datei ist so einfach wie das Komprimieren der richtigen Dateien zusammen und das Ergebnis wird eine XAP-Erweiterung zugewiesen. In diesem Szenario müssen wir eine vorhandene .zip-Datei nehmen — die .xap-Datei ohne Mittel – und die RESX-Datei der entsprechenden Kultur hinzufügen. Um in die zumachenden Funktionalität zu unterstützen, verwenden wir die DotNetZip-Bibliothek, am dotnetzip.codeplex.com. Wir zunächst versucht hatte, die System.IO.ZipPackage verwenden, um die Komprimierung ohne die externe Bibliothek zu tun, aber wir lief in Kompatibilitätsprobleme mit der entstandenen XAP-Datei. Der Prozess sollte möglich mit gerade dem System.IO.ZipPackage-Namespace, aber die DotNetZip-Bibliothek machte es viel einfacher.

Hier ist eine Dienstprogrammmethode erstellten wir helfen mit der Zip-Funktionalität:

public static void AddFileToZip(string zipFile, string fileToAdd, 
  string directoryPathInZip) {
  if (string.IsNullOrEmpty(zipFile) || string.IsNullOrEmpty(fileToAdd)) return;

  using (ZipFile zip = ZipFile.Read(zipFile)) {
    zip.AddFile(fileToAdd, directoryPathInZip);
    zip.Save();
  } }

In der Methode InjectResourceIntoXAP sind wir nur einen Aufruf der AddFileToZip Methode mit einigen Fehlerbehandlung Verpackung:

private bool InjectResourceIntoXAP(FileInfo localizedXapFile, 
  FileInfo localizedResxFile) {
  if (localizedXapFile.Exists && localizedResxFile.Exists) {
    try {
      Utilities.AddFileToZip(localizedXapFile.FullName, 
        localizedResxFile.FullName, string.Empty);
      return true;
    }
    catch { return false; }
  }
  return false;
}

Was wir ursprünglich dachten wollte einer der schwierigsten Teile der Lösung erwies sich als die einfachste. Denken Sie an alle anderen Verwendungen für dynamisch erstellte XAP-Dateien!

Die Datei an den Client übertragen

Wir jetzt zurück bis zur Oberfläche schwimmen und beenden die ProcessRequest-Methode. Wenn wir letzten hier waren, wir hinzugefügt den Code die CreateLocalizedXapFile-Methode aufrufen und den Pfad an die .xap-Datei zurückgegeben, aber wir haben nicht etwas getan, mit dieser Datei. Zu übertragende Dateien an den Client zu unterstützen, werde ich eine andere Dienstprogrammmethode erstellen. Die Methode, genannt TransmitFile, Header, Inhaltstyp und Cacheablaufzeit der Datei festgelegt und anschließend die TransmitFile-Methode der HttpResponse-Klasse verwendet, um die Datei ohne Zwischenspeicherung direkt an den Client zu senden. Abbildung 5 zeigt den Code.

Abbildung 5 Die TransmitFile-Methode

public static void TransmitFile(HttpContext context, string filePath, 
  string contentType, bool deleteFile) {
  if (context == null) return;
  if (string.IsNullOrEmpty(filePath)) return;

  FileInfo file = new FileInfo(filePath);
   try {
     if (file.Exists) {
       context.Response.AppendHeader("Content-Length", file.Length.ToString());
       context.Response.ContentType = contentType;
       if (!context.IsDebuggingEnabled) {                      
         context.Response.Cache.SetCacheability(HttpCacheability.Public);
         context.Response.ExpiresAbsolute = DateTime.UtcNow.AddDays(1);
         context.Response.Cache.SetLastModified(DateTime.UtcNow); 
       }

       context.Response.TransmitFile(file.FullName);
     if (context.Response.IsClientConnected) {
       context.Response.Flush();
     }  }
     else {
       Utilities.SendResponse(context, 404, "File Not Found (" + filePath + ")."); }
     }
     finally {
       if (deleteFile && file.Exists) { file.Delete(); }
     } }

Die ProcessRequest-Methode rufen wir TransmitFile-Methode gibt es den Kontext und den lokalisierten XAP-Dateipfad und nicht zum Löschen der Datei angeben (zwischenzuspeichern) Wenn die Übertragung abgeschlossen ist:

Utilities.TransmitFile(context, localizedXapFilePath, "application/x-silverlight-app", false);

So dass es Arbeit

Zu diesem Zeitpunkt haben wir einen arbeiten XAP-Handler, und jetzt müssen wir es in der Web-Anwendung verbinden. Wir den Handler in den HttpHandlers-Abschnitt der Datei web.config hinzufügen. Der Pfad des Handlers wird der Dateipfad der XAP-Datei mit einem Sternchen vor der Erweiterung eingefügt sein. Dies wird eine Anforderung an die XAP-Datei unabhängig davon die Parameter an den Ereignishandler weiterleiten. Der system.web-Konfigurationsabschnitt mit Cassini und IIS 6 verwendet wird und der system.webServer-Abschnitt ist für die Verwendung mit IIS 7:

<system.web>
    <httpHandlers>
      <add verb="GET" path="ClientBin/CustomLocalization*.xap" 
        type="CustomLocalization.Web.XapHandler, CustomLocalization.Web"/>
    </httpHandlers>
  </system.web>
<system.webServer>
    <handlers>
      <add name="XapHandler" verb="GET" path=
        "ClientBin/CustomLocalization*.xap" 
        type="CustomLocalization.Web.XapHandler, CustomLocalization.Web"/>
    </handlers>
  </system.webServer>

Jetzt, indem Ressourcendateien in Ordner für jedes Gebietsschema auf dem Server verschoben, die Lösung arbeitet. Sobald wir die RESX-Dateien aktualisieren, regeneriert die lokalisierte .xap-Dateien veraltet und sind auf Anforderung. So haben wir eine Lösung entwickelt, die uns eine Silverlight-Anwendung mit Ressourcen für jede Sprache bereitstellen, ohne dass einen einzelnen Webdienst aufrufen können. Jetzt lassen Sie uns nehmen sie einen Schritt weiter. Die Quelle der Wahrheit für Gebietsschema-Informationen ist nicht die RESX-Dateien. Die Quelle der Wahrheit ist die Datenbank, und die RESX-Dateien sind Nebenprodukte der Datenbank. In eine ideale Lösung würden Sie nicht bewältigen RESX-Dateien; Sie würde nur die Datenbank zu ändern, wenn Ressourcen hinzugefügt oder aktualisiert werden. Rechts müssen nun, die RESX-Dateien aktualisiert werden, wenn die Datenbankänderungen, und dies ein mühsamer Prozess, sogar mit einem halbautomatischen Werkzeug sein kann. Im nächste Abschnitt wirft einen Blick auf die zu automatisieren.

Mithilfe eines benutzerdefinierten Ressourcenproviders

Erstellen einer benutzerdefinierten Ressourcenprovider ist ein komplexes Unterfangen und nicht in den Anwendungsbereich dieses Artikels, aber Rick Strahl hat einen gut geschriebene Artikel diskutiert eine ähnliche Implementierung bei bit.ly/ltVajU. In diesem Artikel verwenden wir eine Teilmenge der seine Ressourcenanbieter-Lösung. Die main-Methode, GenerateResXFileNormalizedForCulture, wird unsere Datenbank für die komplette Ressourcensatz einer bestimmten Kultur-Zeichenfolge Abfragen. Wenn die Ressource auf eine Kultur der Standard festgelegt zu konstruieren.NET-Ressource-Manager-Hierarchie wird durch die erste übereinstimmende der invarianten Kultur, dann die Neutral (oder Sprache) Kultur und schließlich die bestimmte Kultur-Ressource für jeden Schlüssel aufrechterhalten.

Beispielsweise eine Anforderung für die En-uns Kultur führen würde, in der Kombination der folgenden Dateien: ui.resx, ui.en.resx und ui.en-us.resx.

Verwenden die eingebetteten Ressourcen

In Teil 1 die Lösung abgerufen alle Ressourcen, die mithilfe von Web Service-Aufrufe, und wenn der Webdienst nicht verfügbar war, würde es zurück in eine Datei im Web-Verzeichnis, das die Standard-Ressourcenzeichenfolgen enthalten fallen. Keines dieser Verfahren ist mehr notwendig. Wir löschen Sie die Datei mit den Standard-Ressourcenzeichenfolgen und entfernen die Anwendungseinstellung, die auf sie verweist. Im nächste Schritt ist ändern Sie die SmartResourceManager, die eingebetteten Ressourcen zu laden, wenn die Anwendung initialisiert. ChangeCulture-Methode ist der Schlüssel für die eingebetteten Ressourcen in die Lösung integriert. Die Methode sieht jetzt wie folgt:

public void ChangeCulture(CultureInfo culture) {
  if (!ResourceSets.ContainsKey(culture.Name)) {
    localeClient.GetResourcesAsync(culture.Name, culture);
  }
  else {
    ResourceSet = ResourceSets[culture.Name];
    Thread.CurrentThread.CurrentCulture = 
      Thread.CurrentThread.CurrentUICulture = culture;
  } }

Anstelle von Anrufen an den GetResourcesAsync-Vorgang sofort, wir versuchen, die Ressourcen aus einer eingebetteten Ressource-Datei zu laden, und wenn dies, dann fehlschlägt machen den Aufruf an den Webdienst. Wenn die eingebetteten Ressourcen erfolgreich geladen, werden wir aktive Ressourcensatz aktualisieren. Hier ist der Code:

if (!ResourceSets.ContainsKey(culture.Name)) {
  if (!LoadEmbeddedResource(culture)) {
    localeClient.GetResourcesAsync(culture.Name, culture);    
  } 
else {
  ResourceSet = ResourceSets[culture.Name];
  Thread.CurrentThread.CurrentCulture = 
    Thread.CurrentThread.CurrentUICulture = culture;
} }

Wir wollen in der LoadEmbeddedResource-Methode ist die Suche nach einer Datei in der Anwendung in das Format von resourceSet.culture.resx. Wenn wir die Datei finden, wollen wir als ein XmlDocument laden, analysieren es in ein Wörterbuch und fügen Sie es dem ResourceSets-Wörterbuch. Abbildung 6 ist, wie der Code aussieht.

Abbildung 6 die LoadEmbeddedResource-Methode

private bool LoadEmbeddedResource(CultureInfo culture) {
  bool loaded = false;
  try {
    string resxFile = "ui." + culture.Name + ".resx";
    using (XmlReader xmlReader = XmlReader.Create(resxFile)) {
      var rs = ResxToDictionary(xmlReader);
      SetCulture(culture, rs);
      loaded = true;
   } }
   catch (Exception) {
     loaded = false;
  }
return loaded;
}

SetCulture-Methode ist trivial; die Ressource festgelegt, wenn ein Eintrag vorhanden ist oder eine fügt, wenn es nicht aktualisiert.

Nachbereitung

Dieser Artikel rundeten die Lösung von Teil 1, Integration von serverseitigen Komponenten XAP und RESX-Dateien verwalten.Mit dieser Lösung besteht keine Notwendigkeit für Web Service-Aufrufe, die Standardressourcen abzurufen.Die Idee der Verpackung der Standardressourcen in einer Anwendung kann erweitert werden, um eine beliebige Anzahl von Ressourcen enthalten, die der Benutzer anfordert.

Diese Lösung verringert die Wartung für die Ressourcenzeichenfolgen benötigt.Generieren von RESX-Dateien aus der Datenbank-on-Demand-Mittel, es wenig Management für die RESX-Dateien benötigt ist.Rick Strahl ist eine nützliche Lokalisierungstools codiert, die Sie verwenden können, um die Gebietsschema-Ressourcen aus der Datenbank gelesen, sie ändern und erstellen die RESX-Dateien!Sie finden das Tool bei bit.ly/kfjtI2.

Es gibt viele Orte in dieser Lösung Haken, so dass Sie es tun, fast, was auch immer Sie wünschen anpassen können.Glücklich Codierung!

Matthew Delisle arbeitet für Schneider Electric auf eine führende Silverlight-Anwendung, die beschäftigt sich mit Einsparunggeld durch Speichern von Energie.

John Brodeur ist ein Softwarearchitekt für Schneider Electric mit umfangreichen Web Entwicklung und Anwendung Design Erfahrung.

Dank der folgenden technischen Experten für die Überprüfung dieser Artikel:Shawn Wildermuth