Torno ancora sull'uso delle risorse in ASP.NET 2.0 perchè ho
scoperto un comportamento che di primo acchito mi ha creato qualche problema. Il
problema derivava dal fatto di aver implementato l'ExpressionBuilder di ho
parlato in un precedente post ed aver inserito le espressioni all'interno di
alcuni Literal nella pagina. Chi avesse provato questo codice si sarà reso conto
che la Culture restituita dal Thread corrente è sempre quella di default del
sistema e che ogni tentativo per modificarla non ha l'esito sperato. In sostanza
quello che accade è che il momento in cui vengono valutate le espressioni nella
pagina è precedente a qualunque altro evento della pagina stessa, precedente
perfino al PreInit e quindi non è possibile impostare la cultura corrente
prelevandola ad esempio da una Session oppure dal profilo utente.
Questo comportamento deriva dal fatto che le espressioni
vengono valutare nel momento in cui la pagina viene costruita e non durante il
normale flusso di esecuzione. Per capire meglio quanto sto dicendo, e la
conseguente soluzione che proporrò, è interessante provare a fare un semplice
test. Preso il codice del post precedente e creata una pagina funzionante che
usa una espressione si può provare ad introdurre un errore nel codice generato
dal CodeDom. Io ho scelto ad esempio di restituire il nome di un metodo che non
esiste. La cosa curiosa è che Visual Studio genera immediatamente un errore, a
differenza di quello che ci si potrebbe attendere, e in seguito a tale errore ci
mostra il sorgente della pagina aspx generata. Ecco riportato il punto relativo
la espression:
@__ctrl.Text =
System.Convert.ToString(
SqlResource.GetObject1("hallo_world"),
System.Globalization.CultureInfo.CurrentCulture);
Riportato in rosso il codice generato da CodeDom, cui ho
aggiunto l'1 finale per causare l'errore. Ora, se con un po' di pazienza si
risale il flusso di esecuzione si arriverà a notare che il punto di partenza
dello stesso è nel metodo override FrameworkInitialize().
FrameworkInitialize è un metodo che in .NET 1.1 non era documentato e il cui uso era sconsigliato.
Risalendo ancora il flusso di esecuzione, stavolta affidandosi a Reflector in
breve si vede che FrameWorkInitialize è il primo metodo che viene
chiamato all'interno della pagina direttamente dalla ProcessRequest quindi molto
prima che gli eventi PreInit e Init si verifichino. Ne deriva che l'unico punto
possibile nella pagina per impostare la CultureInfo è il costruttore.
Per risolvere alla radice questo problema, dato che non mi
piaceva l'idea di usare il costruttore per questo genere di attività, ho
deciso di scrivere un piccolo HttpModule di poche righe che si
incarichi di questa attività. Il vantaggio di usare l'HttpModule è che
estende facilmente il comportamento a tutte le pagine/webservices sotto il
controllo del runtime senza che sia necessario scrivere una classe base per
ognuna di esse. Ecco riportato il codice del Modulo:
public class GlobalizationModule : IHttpModule
{
public void Dispose()
{}
public void Init(HttpApplication context)
{
context.PreRequestHandlerExecute +=
new EventHandler(context_PreRequestHandlerExecute);
}
void context_PreRequestHandlerExecute(object sender, EventArgs e)
{
Thread.CurrentThread.CurrentUICulture =
this.CurrentCulture;
}
private CultureInfo CurrentCulture
{
get
{
return CultureInfo.CreateSpecificCulture(
HttpContext.Current.Profile.GetPropertyValue("Culture") as string);
}
}
}
Il modulo fa uso dell'evento
PreRequestHandlerExecute
che viene eseguito subito prima che il runtime chiami la ProcessRequest
della pagina. In questo modo l'impostazione della cultura avverrà dopo che il
costruttore della pagina è stato eseguito, ma subito prima che si entri nel
normale flusso di esecuzione. Personalmente ho scelto di prelevare la
Culture corrente dal Profile dell'utente collegato. In questo
modo si rende persistente la cultura utilizzata attraverso diverse sessioni
oltre che su diverse chiamate.
Occorre infine segnalare un
particolare rilevante. Visto quello che è il flusso di esecuzione della
chiamata, non ho ancora trovato un metodo valido per cambiare la Culture allo
scattare di un evento di un controllo. Mi spiego meglio: se ad esempio nella
pagina disponiamo della classica DropDownList con tutte le lingue e
volessimo intercettare l'evento SelectedIndexChanged per modificare la
lingua della pagina corrente scopriremmo ben presto che questo ha un
comportamento anomalo. Infatti a ben pensarci quando si entra nell'handler
dell'evento e si imposta la nuova culture, la pagina è stata già generata nella
lingua precedente. Perciò l'unica cosa da fare è di usare un
Response.Redirect(Request.Path) dopo aver impostato la nuova cuture che
otterrà l'effetto di ricaricare completamente la pagina. In questo modo però si
perde completamente lo stato della pagina che poteva essere memorizzato nella
ViewState. La morale di tutto ciò è quindi: Create sempre una pagina di
"profilo utente" che permetta di impostare la Culture prescelta, e togliete di
mezzo bandierine e tendine della lingua... non è una gran
perdita.
powered by IMHO 1.3