Prosegue con questo post la saga, iniziata quando ho parlato
degli ExpressionBuilders, dedicata alle risorse
tipicamente utilizzate nelle applicazioni ASP.NET per depositare stringhe, e in
genere proprietà da utilizzare nel rendering dell'interfaccia. In quel post
spiegavo come realizzare un ExpressionBuilder che permettesse facilmente dei
leggere le risorse da un database anziche da un più consueto file resx. Esiste
tuttavia un altro metodo, per fare la medesima cosa, che in realtà è quello che
il framework indica come quello corretto. Chi avesse provato a realizzare
l'ExpressionBuilder forse si sarà reso conto che pur funzionando egregiamente
esso ha delle limitazioni fastidiose. Giusto per indicarne una per tutte,
quella che a me ha fatto approfondire ulteriormente l'argomento, qualora vi
trovaste a localizzare un SiteMap ben presto vi renderete conto che in
questo l'ExpressionBuilder non vi è di alcun aiuto. Il motivo è presto detto: la
funzione degli ExpressionBuilder non è quella di leggere risorse, ma
semplicemente quello che tipicamente viene usato per questa operazione, il
ResourceExpressionBuilder è solo una minima sfaccettatura all'interno
dell'architettura del framework dedicata all'argomento.
Se come ho fatto io imbracciate il vostro reflector,
e lo puntate sulla classe System.Web.SiteMapNode, vi accorgerete che la
proprietà Title (una di quelle che è passibile di localizzazione),
racchiude al suo interno molta più logica di quella che ragionevolmente ci si
potrebbe aspettare. Ecco un estratto da Reflector:
public virtual string Title
{
get
{
if (this._provider.EnableLocalization)
{
string text1 = this.GetImplicitResourceString("title");
if (text1 != null)
return text1;
text1 = this.GetExplicitResourceString("title", this._title, true);
if (text1 != null)
return text1;
}
if (this._title != null)
return this._title;
return string.Empty;
}
}
Procedendo ulteriormente nell'esplorazione in breve si
scoprira che il codice porta ad istanziare il medesimo
ResourceExpressionBuilder e quindi in definitiva tentare di localizzare
una proprietà della SiteMap corrisponde ad usare l'ExpressionBuilder
"Resources". Di primo acchito questo mi è parso davvero strano. L'idea che mi
ero fatto era che a quel punto si potessero usare esclusivamente i consueti file
resx per localizzare una SiteMap.
In realtà nel framework esiste una interfaccia
IResourceProvider, che è il punto giusto cui agganciarsi per spostare
la fonte delle risorse dai normali file al database. Un po' celato all'interno del DOM del web.config si trova un
attributo dell'elemento <globalization> denominato resourceProviderFactoryType. In tale attributo si dovrà
immettere il tipo, derivato da ResourceProviderFactory, che ha il
compito di creare il ResourceProvider deputato a recuperare le
risorse. Mi rendo conto che
detto così pare un po' complesso, ma questo meccanismo è molto efficace perchè
consente di avere diversi ResourceProvider che lavorano assieme,
istanziati alla bisogna in base al tipo di risorse che si desidera prelevare. In
particolare il ResourceProviderFactory dispone di due metodi, uno per
le risorse cosidette "locali" e uno per quelle "globali". Per
comprendere la distinzione si dovra pensare alle risorse locali come quelle
dedicate ad una singola pagina, infatti il relativo metodo richiede in ingresso
il path della pagina, mentre per globali si intendono quello usate da tutte le
pagine dell'applicazione. In quest'ultimo caso il metodo richiede una
"classKey" cioè un qualificatore che permette di distinguere dei gruppi
di risorse.
Non è finita però. Dopo aver implementato il
ResourceProviderFactory dovremmo procedere all'implementazione di
IResourceProvider che richiede la realizzazione di due metodi. Il primo per la lettura di una
singola risorsa, e il secondo che restituisce un IResourceReader, che serve al runtime per
enumerare le risorse presenti. La classe che implementa IResourceReader sarà lanostra
terza ed ultima fatica. Ecco di seguito il codice di entrambe le
classi:
public class SqlResourceProvider : IResourceProvider
{
public object GetObject(string key, CultureInfo culture)
{
// leggi una singola risorsa
return GetResourceFromDB(key, culture);
}
public System.Resources.IResourceReader ResourceReader
{
get
{
// leggi un set di risorse
return new SqlResourceReader(
GetResourcesFromDB(CultureInfo.CurrentUICulture));
}
}
}
public class SqlResourceReader : IResourceReader
{
private Dictionary<string, object> resources;
public SqlResourceReader(Dictionary<string, object> resources)
{
this.resources = resources;
}
public IDictionaryEnumerator GetEnumerator()
{
return resources.GetEnumerator();
}
public void Close()
{ }
public IEnumerator IEnumerable.GetEnumerator()
{
return resources.GetEnumerator();
}
public void Dispose()
{
Close();
}
}
L'istanza del ResourceProvider andrà creata nel
ResourceProviderFactory che registreremo in configurazione. In questo modo
avremo interposto il nostro provider a tutti i tentativi di lettura di
risorse di localizzazione effettuati da parte dei componenti del framework,
ed in questo modo potremmo facilmente localizzarli. Anche la SiteMap di cui ho
detto poco fa.
powered by IMHO 1.3