Crad's .NET Blog

L'UGIblog di Marco De Sanctis
posts - 190, comments - 457, trackbacks - 70

Persistere il ViewState sul database (o dove ci pare)

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

Print | posted on Tuesday, June 27, 2006 12:27 AM | Filed Under [ .Net 2.0 ASP.NET 2.0 ]

Powered by:
Powered By Subtext Powered By ASP.NET