Scarica l'esempio (VB.NET e C#)
Con questo articolo verrà data una spiegazione ed una soluzione alternativa a tutti gli sviluppatori che finora, iniziando a sviluppare con ASP.NET e guardando il codice sorgente dell'HTML generato a browser, si sono chiesti: "ma cos'è questo campo VIESTATE enorme? C'è un modo per evitare di farlo viaggiare sul client rendendo più leggere le mie pagine?"
Bene. Inizierò spiegando brevemente cos'è il ViewState, e come viene utilizzato dal runtime di ASP.NET, per passare infine ad una soluzione alternativa per poter salvare questo viewstate da qualche parte sul server.
Il ViewState… e ASP.NET
Chi ha iniziato a sviluppare pagine ASP.NET, avrà subito notato che adesso è possibile creare le WebForm in maniera visuale, e programmarle con l'aiuto degli eventi, un po' come avviene per le applicazioni Windows con Visual Basic 6 o Visual Basic .NET.
Sappiamo benissimo però che per poter eseguire del codice sul server, è necessario mandare una richiesta, e quindi la nostra WebForm, anche se noi possiamo non accorgercene, viene continuamente mandata avanti e indietro dal server, ed ogni volta ricreata e renderizzata sul client.
Per poter mantenere lo stato di visualizzazione dei controlli (come ad es. l'elemento selezionato in una DropDownList, oppure il colore di sfondo di una TextBox, ecc.) durante i percorsi di andata e ritorno dal server, il runtime di ASP.NET si avvale della classeStateBag (di cui l'istanza corrente viene restituita dalla proprietà Page.ViewState) per memorizzare tutte le informazioni sullo stato. L'istanza di questa classe viene quindiserializzata (cioè rappresentato in maniera tale da poter essere memorizzata come stringa per es.) e salvata nel famoso campo hidden chiamato "__VIEWSTATE", per essere quindi recuperato ad ogni richiesta e ricreare i controlli con il loro stato.
Questo è quindi il comportamento standard di ASP.NET.
Come sappiamo però, la cosa affascinante del .NET Framework (e quindi anche ASP.NET) è che, pur essendo completo di ogni dettaglio, ci da anche la possibilità di intervenire in molti punti della sua architettura, per adattare le cose in base alle nostre esigenze. Tutto ciò è anche possibile grazie alla tecniche di overriding di metodi, ma anche con la scrittura di moduli http personalizzati, ecc.
In questo caso, per ottenere quello che vogliamo, e per modificare il comportamento di default del Runtime di ASP.NET, ci avvaleremo dell'override. Andremo cioè a riscrivere la logica di 2 metodi già implementati nella classe Page, che è la classe da cui derivano tutte le nostre classi di code-behind per le WebForms, che ASP.NET utilizza per memorizzare e recuperare il viewstate.
Si tratta di due metodi che sono del tutto non documentati, e che vengono richiamati direttamente dal Runtime di ASP.NET, durante l'elaborazione di ogni singola Web Form. Questi due metodi sono:
- LoadPageStateFromPersistenceMedium
- SavePageStateToPersistenceMedium
Andiamo subito a sbirciare nel codice sorgente di questi 2 metodi, che per comodità ho riportato nella pagina ViewStateNormal.aspx, del progetto di esempio allegato all'articolo.
Ecco come più o meno sono stati implementati da Microsoft i 2 metodi nella classe Page:
protected override void SavePageStateToPersistenceMedium(object viewState)
{
// crea un'istanza del serializzatore, e dello StringWriter
LosFormatter formatter = new LosFormatter();
System.IO.StringWriter stateWriter = new System.IO.StringWriter();
// serializza l'oggetto viewState passato alla funzione
formatter.Serialize(stateWriter, viewState);
// memorizza nel campo hidden "__VIEWSTATE" la stringa che rappresenta l'oggetto serializzato
RegisterHiddenField("__VIEWSTATE", stateWriter.ToString());
}
protected override object LoadPageStateFromPersistenceMedium()
{
// crea un'istanza del serializzatore
LosFormatter Format = LosFormatter();
// deserializza e ritorna l'istanza dell'oggetto viewState memorizzato come stringa nel
// campo hidden __VIEWSTATE
return Format.Deserialize(Request.Form["__VIEWSTATE"]);
}
Il metodo SavePageStateToPersistenceMedium riceve in input un object, che rappresenta il viewstate. Questo oggetto deve essereserializzato, in maniera tale da potr essere memorizzato come stringa in una campo hidden. Viene usata quindi la classeLosFormatter, anch'essa non documentata, che non è un formattatore spagnolo :-) , ma è una classe che esegue la "Limited Object Serialization" (LOS) tramite i suoi metodi Serialize (che accetta in input uno Stream o un TextWriter) e Deserialize.
Una volta ottenuta la rappresentazione dell'oggetto come stringa, la si memorizza in un campo hidden.
Nel metodo LoadPageStateFromPersistenceMedium viene invece recuperata la stringa dalla Request "__VIEWSTATE", e viene passata al metodo Deserialize del LosFormatter, per riottenere l'istanza dell'oggetto viewstate.
Ecco come appare quindi il codice sorgente dell'HTML inviato al browser:

La soluzione… memorizziamo il viewstate sul server
A questo punto, sappiamo bene o male come ASP.NET gestisce il viewstate, e siamo quindi in grado di intervenire per far sì che questo viewstate venga salvato da un'altra parte… per es. in una variabile Session !
Vediamo subito come fare. Come abbiamo detto, è necessario riscrivere la logica dei due metodi, eseguendone l'override. Ecco il codice che troverete nella pagina ViewStateInSessionVar.aspx:
protected override void SavePageStateToPersistenceMedium(object viewState)
{
// crea la chiave per memorizzare l'oggetto viewState in una session variable, concatenando
// il sessionID e lo script name della web form corrente
stringsessionKey = Session.SessionID + Request.ServerVariables["SCRIPT_NAME"];
Session[sessionKey] = viewState;
}
protected override object LoadPageStateFromPersistenceMedium()
{
// ricrea la chiave per recuperare l'oggetto viewState dalla variabile session
// e quindi ritornarlo al chiamante
string sessionKey = Session.SessionID + Request.ServerVariables["SCRIPT_NAME"];
return Session[sessionKey];
}
Come potete vedere il tutto si riduce alla scrittura di poche righe di codice. Nel metodo SavePageStateToPersistenceMedium non facciamo altro che memorizzare l'oggetto, passato in input, in una variabile Session, che di per sé è in grado di ospitare qualsiasi oggetto che derivi da object (e cioè tutti gli oggetto in .NET framework). Questa volta quindi non ci serve rappresentare l'oggetto come una stringa tramite la serializzazione, in quanto lo andiamo a memorizzare direttamente in memoria in una variabile Session che risiede sul server.
L'aspetto interessante è però la generazione della chiave univoca necessaria per memorizzare il nostro oggetto nella session per fare in modo da non confonderlo con altri oggetti memorizzati nella stessa sessione.
La chiave univoca è quindi formata da una concatenazione di SessionID (la chiave che rappresenta la sessione corrente) e lo SCRIPT_NAME della WebForm corrente.
Nel metodo LoadPageStateFromPersistenceMedium invece, non facciamo altro che ricostruirci la chiave univoca e recuperare l'oggetto dalla variabile di sessione.
OK, ci siamo riusciti! Abbiamo salvato sul server il viewstate! Infatti se adesso andate ad eseguire la WebForm e a guardare il sorgente dell'html mandato al browser, vedrete che il campo hidden non esiste più, ma che lo stato dei controlli continua ad essere mantenuto durante i postback:

Conclusioni
Come abbiamo visto siamo arrivati alla soluzione che volevamo. E' necessario però fare alcune osservazioni:
- è ovvio che non si deve esagerare nel memorizzare sul server dei viewstate enormi, in quanto con la soluzione della session, il tutto rimane nella memoria RAM del server, occupando risorse preziose per l'elaborazione delle pagine. Quindi è consigliabile usare questa soluzione solo se necessario;
- per il motivo esposto sopra è quindi opportuno fare a meno il più possibile del viewstate, disabilitandolo per quei controlli che lo usano parecchio, come ad es. il DataGrid, impostando la proprietà EnableViewState (presente in ogni controllo, in quanto ereditata da Control) su false;
- se memorizzare in session occupando memoria preziosa sul server non è opportuno per la vostra applicazione, potete sempre riscrivere i 2 metodi per far in modo che l'oggetto venga salvato in un campo di un database per es.. Tenendo presente che se si deve memorizzare l'oggetto come stringa allora è necessario serializzarlo con LosFormatter, e che possono nascere alcuni problemi come ad es.: "come faccio a sapere quando eliminare le informazioni non più utilizzate dal DB?"
Lascio a voi quindi, come sempre, il piacere di sperimentare le vostre soluzioni ad-hoc, prendendo come spunto quella da me proposta. Alla prossima …!
posted on giovedì 5 maggio 2005 0.49
by
Stefano Giannone