Il viewstate di ASP.NET alle volte è una gran comodità,
che però paghiamo in termini di prestazioni ad ogni postback, dato che un bel
campo hidden di grandezza che a volte può essere considerevole, è costretto ad
andare avanti e indietro tra client e server. La cosa bella, però, è che sin
dalla prima versione di ASP.NET ci è data la possibilità di inventare il nostro
piccolo engine di persistenza del viewstate, che invece che un hidden field
utilizzi una tabella su DB, il file system, o qualsiasi altro supporto. In
questo piccolo esempio utilizzeremo SQL Server
Prima di tutto la tabella su DB: come identifichiamo un Viewstate? L'idea è
quella di inserire in ogni pagina un campo hidden valorizzato con un guid, che
possiamo utilizzare per risalire al viewstate relativo alla stessa. Poi, sarebbe
bene memorizzare anche il sessionId, in modo da eliminare tutti i viewstate
relativi ad una certa sessione una volta che questa scada; in pratica, quindi,
la nostra tabella sarà simile alla seguente:
Bene, per realizzare un engine di persistenza per il
viewstate si deve creare una classe che erediti dalla classe astratta PageStatePersister
. Essa presenta
due metodi astratti dai nomi fin troppo autoesplicativi:
public abstract class PageStatePersister
{
public abstract void Load();
public abstract void Save();
// more code here....
}
e una proprietà readonly che ci sarà molto utile tra pochissimo:
protected IStateFormatter StateFormatter { get; }
Questa proprietà
restituisce un'oggetto che implementa IStateFormatter
, che possiamo utilizzare per serializzare il nostro viewstate.
Mettiamoci all'opera costruendo per prima la nostra implementazione per il
metodo Save:
public class DBPageStatePersister
: PageStatePersister
{
public DBPageStatePersister(Page page)
: base(page)
{}
public override void Save()
{
if (ViewState != null || ControlState != null)
{
// abbiamo qualcosa da salvare, costruiamo un oggetto Pair
Pair states = new Pair(ViewState, ControlState);
DBViewStateGateway gateway = new DBViewStateGateway();
Guid viewStateGuid = gateway.SaveViewState(
Page.Session.SessionId,
StateFormatter.Serialize(states));
Page.ClientScript.RegisterHiddenField(
"__GVIEWSTATE", viewStateGuid.ToString())
}
}
}
Ciò che
viene fatto è innanzitutto tutto creare un oggetto Pair che contenga al suo
interno i due dati da memorizzare, ovvero il ViewState e il ControlState. Poi instanziamo un oggetto di tipo
DBViewStateGateway (di cui parlerò dopo) il cui compito è quello di gestire
concretamente le operazioni da e verso il DB. Un opportuno metodo SaveViewState
viene invocato dopo aver serializzato le informazioni di stato, esegue una query
di insert nella tabella del DB e restituisce il guid assegnato alla nuova entry;
tale guid viene memorizzato all'interno di un campo hidden denominato
"__GVIEWSTATE" nella pagina in procinto di
essere restituita al client.
Metà lavoro è fatto, diamo un'occhiata al metodo
Load:
public override void Load()
{
DBViewStateGateway gateway = new DBViewStateGateway();
Pair state = (Pair) StateFormatter.Deserialize(
gateway.LoadViewState(Page.Session.SessionId,
Page.Request["__GVIEWSTATE"]);
ViewState = state.First;
ControlState = state.Second;
}
Anche in questo caso non c'è nulla di complesso:
tramite il solito gateway recuperiamo da DB il Pair con le informazioni di
stato, lo deserializziamo e riassegniamo i valori che erano stati
precedentemente memorizzati.
Come dico
ad ASP.NET di utilizzare questo engine di persistenza dello stato? Devo
realizzare un semplicissmo custom PageAdapter
public class CradPageAdapter: PageAdapter
{
public override PageStatePersister GetStatePersister()
{
return new DBViewStatePersister(Page);
}
}
e
indicare in un file .browser all'interno di App_Browsers
di utilizzare questo adapter come default:
<browsers>
<browser refID="Default" >
<controlAdapters>
<adapter
controlType="Crad.WebControls.PageBase"
adapterType="Crad.WebControls.CradPageAdapter" />
</controlAdapters>
</browser>
</browsers>
Spostiamo ora la nostra attenzione su
DBViewStateGateway, che si occupa di interagire con il DBMS. Non mi dilungo
molto sulla concreta implementazione, visto che è estremamente semplice
(dopotutto si tratta di eseguire solo un paio di semplicissime query ), ma diamoci comunque
un'occhiata:
internal class DBViewStateGateway
{
public DBViewStateGateway
{
// qui leggo la connection da file di configurazione
// ....
}
public Guid SaveViewState(string sessionId, string state)
{ ..... }
public string LoadView(string sessionId, Guid stateGuid)
{ ..... }
// Due metodi importanti
public void DeleteSessionStates(string sessionId)
{ ..... }
public void DeleteAllStates()
{ ..... }
}
I due metodi che ho segnalato vanno invocati dal Global.asax
, rispettivamente in corrispondenza di Session_End e
Application_Start e servono per eliminare dalla tabella le righe di viewstate
che sono diventate obsolete o perché la rispettiva sessione è scaduta, o a causa
di un riavvio dell'applicazione stessa.
Possibili migliorie? Beh... a dire il vero l'implementazione che ho realizzato dell'engine di persistenza (leggermente differente da quella che ho mostrato qui)
è disaccoppiata dal
PageStatePersister, il che mi permette di realizzare una inversion of control e
di selezionare il sistema che preferisco (DB o FileSystem, ad es.) con una
semplice variazione all'interno del file di configurazione. Ma questo esempio è
"didatticamente" più semplice e non mi andava di appesantire ulteriormente la
trattazione che è già lunga di per sé.
Ciao!
powered by IMHO 1.3