Cache di pagina
La cache di pagina viene impostata con la direttiva di pagina OutputCache ed è particolarmente efficiente, perché permette al motore Asp.NET di riutilizzare l’html generato da una richiesta precedente senza eseguire nemmeno una riga di codice. Questa tecnica non è però priva di effetti collaterali, che debbono essere accuratamente esaminati per capire se effettivamente si può applicare alle proprie pagine e/o controlli. Si prenda ad esempio la pagina page1.aspx contenuta nel codice accluso che contiene una semplice gridView collegata alla tabella customer del database northwind. L’unica particolarità è la presenza di una colonna template che contiene un bottone il cui scopo è cancellare un cliente dal database.
Nell’esempio accluso la routine non effettua veramente la cancellazione ma indica semplicemente in una label l’id cliente che sarebbe stato cancellato, in questo modo non si alterano i dati nel database e l’esempio non perde di validità.
La pagina ha quindi un funzionamento molto semplice, vediamo ora cosa succede se si abilita la cache.
Abilitare la cache di pagina
Sempre nell’esempio accluso, la pagina page2.aspx contiene lo stesso codice visto in precedenza, con la semplice aggiunta della direttiva di abilitazione della cache
Se si esegue la pagina si nota però che alla prima pressione del bottone il comportamento è quello corretto e viene indicata la richiesta di cancellazione, ma alle pressioni successive non accade nulla e se si mette un breakpoint in modalità debug si può verificare che la routine GridView1_RowCommand viene chiamata solamente la prima volta.
Chi conosce bene la cache di pagina non rimarrà sorpreso da questo comportamento, ma analizziamo comunque in dettaglio cosa succede per avere un quadro della situazione.
Alla prima richiesta il server
non ha la pagina in cache, viene quindi generata la pagina in modo normale e l’html risultante viene salvato nella cache. Alla prima pressione di un bottone la richiesta arriva al server, anche questa volta viene controllata la cache di pagina e
nuovamente si ha un cache miss perché questa volta la richiesta è in POST mentre precedentemente era in get. Alle successive pressioni del tasto la pagina viene trovata in cache, viene quindi restituito al chiamante l’html contenuto nella cache e
nessun evento viene generato perché non viene nemmeno creata un’istanza della classe che gestisce la pagina
Questo tipo di cache può quindi aumentare di molto le prestazioni di un’applicazione Asp.NET perché evita completamente l’esecuzione di codice. Si consideri inoltre che la cache è abilitabile anche per i singoli controlli utente e quindi è possibile effettuare cache parziale del contenuto di una pagina.
Purtroppo nel caso in cui si richieda un comportamento dinamico, come nell’esempio appena mostrato, questa tecnica non è apparentemente utilizzabile perché purtroppo elimina la chiamata dei metodi lato server che eseguono logica di business.
Forzare l’esecuzione della pagina indipendentemente dalla cache
Supponiamo che la pagina precedente venga utilizzata per il 99.99% delle volte in modalità sola lettura e solo in rarissimi casi venga richiesta una cancellazione. In questo scenario il cache di pagina è legittimo, bisogna solamente trovare il modo di forzare un cache miss alla pressione di un bottone. Naturalmente per pagine fortemente interattive questa tecnica non è assolutamente efficiente, perché si genererebbero troppe versioni nella cache e sarebbe quindi consigliabile adottare altre strategie di caching.
La direttiva OutputCache permette di indicare come viene indicizzata la cache tramite vari attributi come varyByParam, varyByControl etc, ed è proprio su questi che si deve agire per forzare un cache miss in maniera programmatica. In particolare il varyByParam permette di specificare come chiavi di indicizzazione nomi di parametri in GET o in POST. Supponiamo quindi di aggiungere la linea
Alla direttiva OutputCache. In questo caso se la pagina viene chiamata con page2.aspx?param=1 il motore di asp.net indicizzerà l’output nella cache con il valore param=1. Se una successiva richiesta viene fatta ad esempio con page2.aspx?param=2 il risultato è un cache miss e l’output viene inserito a fianco del precedente, indicizzato con il valore param=2. In questo scenario è importante capire che per ogni differente combinazione dei parametri indicati viene mantenuta nel server una versione della pagina in cache.
Il nostro problema è comunque differente, quello che si desidera è abilitare la cache, ma far si che alla pressione di determinati bottoni si abbia sempre un cache miss, perché siamo interessati all’esecuzione del codice lato server associato al bottone stesso. Almeno apparentemente non esiste però una direttiva varyByXxx che permetta di indicare ad asp.NET uno o più bottoni che invalidano la cache, ma il problema può essere aggirato in maniera molto semplice.
La soluzione è contenuta nella page3.aspx, se la si manda in esecuzione si può vedere come sia stata aggiunta una label che riporta l’ora di generazione della pagina, necessaria per verificare che effettivamente le pagine siano nuovamente rigenerate e non prese dalla cache. Aprendo un secondo browser e navigando allo stesso indirizzo si può notare come l’ora restituita sia quella del primo caricamento, questo banale esperimento ci mostra che la cache è effettivamente funzionante. La differenza è che questa volta i bottoni funzionano correttamente ed il codice dell’handler viene eseguito ad ogni click.
Il trucco è veramente banale, prima di tutto è stato inserito un HiddenField di nome forcePostBack il cui valore iniziale è un guid, e poi la cache è stata impostata in modo da variare sulla base del contenuto di questo controllo. Se si vuole che alla pressione di un bottone sia sempre eseguito il codice del corrispettivo handler è sufficiente cambiare il contenuto dell’hiddenField con un altro valore prima di effettuare il postback, assicurandosi chiaramente che questo valore sia univoco. Per ottenere un nuovo guid ad ogni pressione di un bottone è indubbiamente vantaggioso generare direttamente lato server il codice javascript associato all’evento OnClientClick di ogni bottone. Ecco ad esempio come vengono creati i bottoni della gridView.
Nello snippet è stato evidenziato come l’unica aggiunta sia l’attributo OnClientClick il cui contenuto è impostato da una funzione lato server chiamata ForcePostbackJs().
Come si può vedere ad ogni evento load la pagina genera un nuovo guid e alla pressione del bottone si associa un javascript che imposta il contenuto dell’hiddenfield al nuovo guid generato. In questo modo si ha la sicurezza che ad ogni pressione di un bottone il nuovo valore dell’hidden field sia univoco e generi sempre un cache miss.
Il quadro non è però ancora completo, se si inserisce la vera logica di cancellazione, dopo la pressione del bottone alle successive richieste della pagina verrebbe comunque restituita la versione contenuta nella in cache, in cui è ancora presente l’elemento cancellato. In generale infatti se la pressione di un bottone provoca l’esecuzione di logica di business che modifica i dati sulla base dei quali è generata la pagina, tutte le precedenti versioni in cache non sono più valide. La soluzione è quindi invalidare la cache di pagina in maniera programmatica, inserendo l’istruzione seguente nell’handler del bottone
In questo modo ogni volta che viene eseguita logica di business tutta la cache della pagina Page3.aspx viene invalidata e le successive richieste visualizzeranno dati corretti.
Conclusioni
La cache di pagina è una tecnica che permette di ottenere grandi incrementi di prestazioni per pagine statiche o che contengono piccole porzioni dinamiche, purtroppo è necessario ricorrere a piccoli “trucchi” se si vuole che la logica lato server risponda sempre agli eventi utente. In generale comunque la possibilità di abilitare questa forma di caching anche per i singoli controlli la rende indubbiamente una tecnica di grande efficacia per le proprie applicazioni Asp.NET.