2011-01-10 3 views
5

Ich arbeite an einer Webanwendung, die ich lokalisieren und internationalisieren muss. Es kam mir in den Sinn, dass ich dies mithilfe eines Abhängigkeits-Injection-Frameworks tun könnte. Lassen Sie uns sagen, dass ich eine Schnittstelle ILocalResources erklären (C# für dieses Beispiel verwenden, aber das ist nicht wirklich wichtig):Lokalisierung mit einem DI-Framework - gute Idee?

interface ILocalResources { 
    public string OkString { get; } 
    public string CancelString { get; } 
    public string WrongPasswordString { get; } 
    ... 
} 

und erstellen Implementierungen dieser Schnittstelle, eine für jede Sprache, die ich unterstützen müssen. Ich würde dann mein DI-Framework einrichten, um die korrekte Implementierung entweder statisch oder dynamisch (zum Beispiel basierend auf der bevorzugten Sprache des anfordernden Browsers) zu instanziieren.

Gibt es einen Grund, warum ich ein DI-Framework für so etwas nicht verwenden sollte? Der einzige Einwand, den ich finden könnte, ist, dass es vielleicht etwas übertrieben ist, aber wenn ich sowieso ein DI-Framework in meiner Web-App verwende, könnte ich es genauso gut für die Internationalisierung verwenden?

Antwort

2

Wenn Sie ein vorhandenes Ressourcenframework (wie das in ASP.Net integrierte) nicht verwenden können und Ihr eigenes erstellen müssen, gehe ich davon aus, dass Sie irgendwann Dienste bereitstellen müssen, die lokalisierte Ressourcen bereitstellen.

DI-Frameworks werden für die Instanziierung von Services verwendet. Ihr Lokalisierungsframework stellt Dienste zur Verfügung, die Lokalisierung bereitstellen. Warum sollte dieser Service nicht durch das Framework bedient werden?

Die Verwendung von DI für seinen Zweck ist hier nicht so, als würde ich sagen: "Ich baue eine CRM-App, kann DI aber nicht verwenden, weil DI nicht für die Verwaltung von Kundenbeziehungen gebaut wurde".

Also, wenn Sie DI bereits im Rest Ihrer Anwendung verwenden, IMO wäre es falsch, es nicht für die Dienste zu verwenden, die die Lokalisierung behandeln.

2

Der einzige Nachteil, den ich sehen kann, ist, dass für jedes Update auf "Ressourcen" müssten Sie die Assembly mit Ressourcen neu kompilieren. Abhängig von Ihrem Projekt kann dieser Nachteil ein guter Rat sein, nur ein DI-Framework zu verwenden, um einen ResourceService irgendeiner Art zu lösen, und nicht die Werte selbst.

6

Ein DI-Framework ist gebaut, um Abhängigkeitsinjektion und Lokalisierung zu tun könnte nur einer Ihrer Dienste sein, so dass in diesem Fall gibt es keinen Grund nicht ein DI-Framework IMO zu verwenden. Vielleicht sollten wir anfangen, die bereitgestellte ILocalResources Schnittstelle zu diskutieren. Obwohl ich der Kompilierzeit-Support bin, bin ich nicht sicher, ob die mitgelieferte Schnittstelle Ihnen helfen wird, da diese Schnittstelle wahrscheinlich der Typ in Ihrem System sein wird, der sich am meisten ändert. Und mit dieser Schnittstelle die Art/Typen, die es implementieren. Vielleicht sollten Sie mit einem anderen Design gehen.

Wenn wir uns die meisten Lokalisierungsframeworks/Provider/Factories (oder was auch immer) anschauen, sind sie alle stringbasiert. Aus diesem Grund, denken Sie an die folgende Konstruktion:

public interface ILocalResources 
{ 
    string GetStringResource(string key); 
    string GetStringResource(string key, CultureInfo culture); 
} 

Dies würde erlauben Sie Schlüssel und Kulturen auf die zugrunde liegende Nachrichtendatenspeicher hinzuzufügen, ohne die Schnittstelle zu ändern. Nachteil ist natürlich, dass Sie niemals einen Schlüssel ändern sollten, denn das wird wohl eine Hölle sein.

Ein weiterer Ansatz könnte eine abstrakte Basistyp sein:

public abstract class LocalResources 
{ 
    public string OkMessage { get { return this.GetString("OK"); } } 
    public string CancelMessage { get { return this.GetString("Cancel"); } } 
    ... 

    protected abstract string GetStringResource(string key, 
     CultureInfo culture); 

    private string GetString(string key) 
    { 
     Culture culture = CultureInfo.CurrentCulture; 

     string resource = GetStringResource(key, culture); 

     // When the resource is not found, fall back to the neutral culture. 
     while (resource == null && culture != CultureInfo.InvariantCulture) 
     { 
      culture = culture.Parent; 
      resource = this.GetStringResource(key, culture); 
     } 

     if (resource == null) throw new KeyNotFoundException(key); 

     return resource; 
    } 
} 

und Implementierung dieser Art könnte wie folgt aussehen:

public sealed class SqlLocalResources : LocalResources 
{ 
    protected override string GetStringResource(string key, 
     CultureInfo culture) 
    { 
     using (var db = new LocalResourcesContext()) 
     { 
      return (
       from resource in db.StringResources 
       where resource.Culture == culture.Name 
       where resource.Key == key 
       select resource.Value).FirstOrDefault(); 
     } 
    } 
} 

Dieser Ansatz Beste aus beiden Welten nimmt, da die Schlüssel gewonnen‘ t durch die Anwendung verstreut werden und neue Eigenschaften hinzugefügt werden, muss nur an einem einzigen Ort erfolgen. Verwenden Sie Ihre favorite DI Bibliothek können Sie eine Implementierung wie folgt registrieren:

container.RegisterSingleton<LocalResources>(new SqlLocalResources()); 

Und da die LocalResources Typ genau eine abstrakte Methode, die die ganze Arbeit tut, ist es einfach, ein Dekorateur zu erstellen, die Caching anfordernden fügt zu verhindern die gleichen Daten aus der Datenbank:

public sealed class CachedLocalResources : LocalResources 
{ 
    private readonly Dictionary<CultureInfo, Dictionary<string, string>> cache = 
     new Dictionary<CultureInfo, Dictionary<string, string>>(); 
    private readonly LocalResources decoratee; 

    public CachedLocalResources(LocalResources decoratee) { this.decoratee = decoratee; } 

    protected override string GetStringResource(string key, CultureInfo culture) { 
     lock (this.cache) { 
      string res; 
      var cultureCache = this.GetCultureCache(culture); 
      if (!cultureCache.TryGetValue(key, out res)) { 
       cultureCache[key] = res= this.decoratee.GetStringResource(key, culture); 
      }     
      return res; 
     } 
    } 

    private Dictionary<string, string> GetCultureCache(CultureInfo culture) { 
     Dictionary<string, string> cultureCache; 
     if (!this.cache.TryGetValue(culture, out cultureCache)) { 
      this.cache[culture] = cultureCache = new Dictionary<string, string>(); 
     } 
     return cultureCache; 
    } 
} 

Sie den Dekorateur anwenden können wie folgt:

container.RegisterSingleton<LocalResources>(
    new CachedLocalResources(new SqlLocalResources())); 

Do not e dass dieser Decorator die String-Ressourcen unbegrenzt zwischenspeichert, was zu Speicherlecks führen kann. Daher möchten Sie die Strings in WeakReference Instanzen verpacken oder eine Art Ablaufzeitlimit dafür haben. Die Idee ist jedoch, dass Sie Caching anwenden können, ohne eine vorhandene Implementierung ändern zu müssen.

Ich hoffe, das hilft.

+0

Alternativ können Sie auch Erweiterungsmethoden für die 'ILocalResources' Schnittstelle, aber das abwärts gerichtete ist implementieren, dass Sie Methoden anstelle von Eigenschaften haben. Aber das wäre wahrscheinlich nicht so schlimm. – Steven