ASP.NET Caching - Data caching

Il Data caching è il sistema di caching più flessibile, ma anche quello dove bisogna scrivere più codice per implementarlo.

Il concetto alla base del Data caching è molto semplice: inserire in una speciale collezione di oggetti (Cache), tutti quegli oggetti che per esser creati utilizzano un certo quantitativo di risorse.

Questa collection è simile alla collection Application, comunque esistono un paio di differenze:

  • Thread-Safe: a differenza dell'Application non avremo bisogno di attivare un sistema di lock/unlock sulla collection Cache quando andremo a rimuovere/aggiungere oggetti, pur essendo anche questa a scope globale.
  • Scavenging: gli oggetti verranno rimossi automaticamente quando risulteranno scaduti, o quando cambieranno, o quando a cambiare sarà una sua dipendenza, o quando le risorse a disposizione del server saranno troppo basse.
    Questo farà si che, non dovremo noi preoccuparci delle risorse del sistema. Ma dovremo stare attenti a controllare se, l'oggetto che stiamo richiedendo, esiste ancora prima di utilizzarlo, altrimenti potremmo incappare in un NullReferenceException.
  • Dependencies: gli oggetti possono essere collegati a files, tabelle di database o qualsiasi altro tipo di risorsa.
    Se la risorsa cambia, l'oggetto in Cache sarà automaticamente ritenuto invalido e rilasciato.

 

Aggiungere un oggetto

Come nella collection Application aggiungere un oggetto in Cache è alquanto semplice:

Cache["key"] = new Int32();

Questo però è il metodo meno utile, in quanto non da la possibilità di specificare quanto tempo l'oggetto deve esser tenuto in cache.

Per questo motivo tornerà utile il metodo Cache.Insert()

Tale metodo offre 4 overloads. Il più complicato, nonchè il più utile, è il seguente:

Cache.Insert(key,
                     
value,
                      dependencies,
                      absoluteExpiration,
                      slidingExpiration,
                      priority,
                      onRemoveCallback);

        protected void Page_Load(object sender, EventArgs e)
        {
            Cache.Insert("key", new object(), null, DateTime.MaxValue, TimeSpan.FromMinutes(10),
                CacheItemPriority.Normal, onRemoveCallback);
        }
        private void onRemoveCallback(string key, object value, CacheItemRemovedReason reason)
        {
            throw new NotImplementedException();
        }

  • key e value: rispettivamente identificano la chiave e il valore dell'oggetto da tenere in cache.
  • dependencies: identifica le dipendente, se ne ha, dell'oggetto. Se non esistono dipendenze basterà settare il valore a null.
  • absoluteExpiration e slidingExpiration: identificano il tempo di vita dell'oggetto.
    Mentre absoluteExpiration settiamo una data specifica per l'oggetto dovrà essere rimosso, ciò può tornarci utile per delle previsioni del tempo o offerte da visualizzare per un periodo conosciuto; slidingExpiration identifica il tempo di vita dell'oggetto prima che possa essere rimosso, utile magari quando vogliamo salvare una ricerca per un periodo limitato di tempo o i dati di un prodotto in catalogo.
    Quando vogliamo attivare absoluteExpiration dovremo settare slidingExpiration con il valore:
    TimeSpan.Zero
    Se, invece, ci interesserà attivare slidingExpiration dovremo settare il valore di absoluteExpiration a:
    DateTime.MaxValue
  • priority: ha effetto solamente se ASP.NET avrà bisogno di attivare il processo di scavenging, prima descritto.
    Quando ASP.NET troverà degli oggetti non usati in cache, prima di rimuoverli, controllerà la priorità settata.
    Solitamente si setta una priorità alta per quegli oggetti che prendono più tempo per essere ricostruiti.
    I valori possibili
    sono:
    • High - l'oggetto verrà rimosso per l'ultimo rispetto agli altri
    • AboveNormal
    • Normal
    • BelowNormal
    • Low - l'oggetto verrà rimosso per primo rispetto agli altri.
    • NotRemovable - l'oggetto non verrà cancellato durante il processo di scavenging

  • onRemoveCallback: è un delegate che verrà chiamato ogni qual volta un oggetto viene rimosso dalla cache.
    Utile per notificare all'applicazione se un oggetto non è più disponibile.

ASP.NET Caching - Web.config

In tutti i precedenti post, per attivare il caching sulla pagina, è stato inserita la direttiva:

<%@ OutputCache %>

Finchè si parla di esempi o finchè si lavora su una singola pagina possiamo inserire quest'opzione.
Ma se dovessimo lavorare su dozzine di pagine e poi magari modificarle, ciò comporterebbe un lavoro estenuante.

ASP.NET, tramite la feature chiamata cache profiles, ci da la possibilità di applicare gli stessi settaggi ad un gruppo di pagine.

Per usare questa caratteristica definiremo i settaggi della cache in un web.config file, assoceremo un nome a questi settaggi e applicheremo i settaggi alle pagine da tenere in cache:

      <configuration>                                                                              
        <system.web>                                                                               
          <caching>                                                                                
            <outputCacheSettings>                                                                  
              <outputCacheProfiles>                                                                
                <add name="ProductItemCacheProfile" duration="60" varyByParam="ProductItem;Type" />
</outputCacheProfiles> </outputCacheSettings> </caching> </system.web> </configuration>

Ed adesso useremo il profilo appena creato sulle nostre pagine:

<%@ OutputCache CacheProfile="ProductItemCacheProfile" VaryByParam="None" %>

DEBUG

Nel nostro web.config file abbiamo la possibilità di dettagliare ancor di più la sezione <caching>.

Molte di queste opzioni sono state create per rendere più semplice la fase di debugging dell'applicazione e non hanno senso in un'applicazione in produzione.

Il tag usato per queste opzioni è <cache>:

      <caching>                                  
        <cache disableMemoryCollection="true"    
           disableExpiration="true"              
           percentagePhysicalMemoryUsedLimit="60"
           privateBytesLimit="52428800"          
           privateBytesPollTime="00:05:00" />    
      </caching>                                 
  • disableMemoryCollection e disableExpiration: specificano se ASP.NET non deve più tenere in cache copia degli oggetti quando abbiamo poca memoria libera nel server e se gli oggetti scaduti devono essere rimossi.
  • percentagePhysucalMemoryUsedLimit: specifica la percentuale massima di memoria del server che ASP.NET può usare per gli oggetti in cache.
    Specificando 0 gli oggetti verranno eliminati il più velocemente possibile.
  • privateBytesLimit: determina il numero massimo di bytes utilizzabili prima che ASP.NET inizi a liberare la memoria.
    Specificando 0 gli oggetti verranno eliminati secondo l'algoritmo interno di ASP.NET.
  • privateBytePollTime: indica quanto spesso ASP.NET deve controllare i bytes usati.
    Il valore di default è di 1 secondo.

ASP.NET Caching - Fragment e Post Caching

Spesso tornerà utile non copiare in cache l'intera pagina ma una parte di questa.

Per ottenere questo risultato abbiamo due tecniche:

  • Fragment caching: Prendiamo i dati che vogliamo copiare in cache e li spostiamo all'interno di uno user control creato appositamente; copiamo in cache solo lo user control creato.
  • Post-cache substitution: Identifichiamo il contenuto dinamico che non vogliamo copiare in cache e cambiamo il contenuto con il controllo Substitution.

Il Fragment caching è il più semplice dei due.
Comunque bisogna basarsi sulla quantità dei dati da tenere in cache, per scegliere quale delle due tecniche applicare.

Se abbiamo una piccola parte di dati che vogliamo tenere in cache, allora il Fragment caching tornerà al caso nostro. Se invece abbiamo solamente una piccola parte di contenuto dinamico, il Post-cache substitution è quello che farà per noi.

 

Implementazione

 

Fragment caching

Implementare il Fragment caching è alquanto semplice.
Basta creare un user control per la parte di codice che vogliamo copiare in cache.
Aggiungeremo la direttiva:

 

<%@ OutputCache Duration="20" VaryByParam="None" %>

 

nell'user control.

Vediamo se tutto realmente funziona.

Creiamo un'user control con questo codice:

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="WebUserControl1.ascx.cs" Inherits="WebApplication1.WebUserControl1" %>
<%@ OutputCache Duration="20" VaryByParam="None" %>
    <div>
        <asp:Label runat="server" ID="lblDate" />
    </div>
        protected void Page_Load(object sender, EventArgs e)
        {
            lblDate.Text = "Tempo:<br/>";
            lblDate.Text += DateTime.Now.ToString();
        }

E una bella pagina Default.aspx così:

    <form id="form1" runat="server">
    <div>
        <asp:Label runat="server" ID="lblDate" />
    </div>
    <div>
        <uc1:WebUserControl1 ID="WebUserControl11" runat="server" />
    </div>
    </form>
        protected void Page_Load(object sender, EventArgs e)
        {
            lblDate.Text = "Tempo:<br/>";
            lblDate.Text += DateTime.Now.ToString();
        }

Proviamo la pagina e, ad ogni refresh, il contenuto dell'user control non verrà aggiornato a differenza della label nella pagina Default.aspx.

Post-cache substitution

Il Post-cache substitution è facile da implementare tanto quanto il Fragment caching.
Quel che cambia è il concetto alla base, è l'inverso del Fragment caching.

L'implementazione seguente è lato markup nella pagina Default.aspx, lo stesso effetto potremmo farlo solamente lato c#:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication1._Default" %>

<%@ OutputCache Duration="20" VaryByParam="None" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    Copiato in cache:
    <asp:Label ID="lblDate" runat="server" /><br />
    Non copiato in cache:
    <asp:Substitution ID="Substitution1" runat="server" MethodName="GetDate" />
    </form>
</body>
</html>

Mentre lato codice:

        protected void Page_Load(object sender, EventArgs e)
        {
            lblDate.Text = DateTime.Now.ToString() + "<br />";
        }

        private static string GetDate(HttpContext context)
        {
            return "<b>" + DateTime.Now.ToString() + "</b>";
        }

Il metodo GetDate è statico perchè ASP.NET deve poter chiamare questo metodo anche quando la pagina non ha un'istanza disponibile (questo accade quando viene usata la pagina copiata in cache).

Anche in questo caso, come detto nei post precedenti, la page life cycle della nostra pagina non  verrà eseguita. Ciò significa che non verrà creato alcun oggetto e nessun evento sarà lanciato.

Se il contenuto dinamico dipende da qualche valore creato a run-time o da un'altro controllo, allora avremo bisogno di basarci su un'altra tecnica di caching che vedremo nei prossimi post.