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<stringobject> resources;

    
public SqlResourceReader(Dictionary<stringobject> 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


per leggere il post originale o inviare un commento visita il seguente indirizzo: ASP.NET 2.0: I ResourceProvider