ASP.NET Caching - Dipendenze

ASP.NET supporta la cache dependencies; questa feature permette di relazionare un oggetto che abbiamo in cache con un'altra risorsa, in modo tale che, quando questa risorsa cambia, l'oggetto in cache verrà rimosso automaticamente.

Esistono tre tipi di dipendenza:

  • Con un'altro oggetto in cache
  • Con un file o una directory
  • Query su database

Per creare una dipendenza nella cache, bisognerà creare un oggetto CacheDependency ed usarlo quando inseriremo un oggetto in cache.

Per esempio, il codice seguente, creerà una dipendenza con un file XML.
Se il file verrà cambiato, cancellato o sovrascritto, l'oggetto in cache scadrà:

CacheDependency prodDependency = new CacheDependency(Server.MapPath("ProductList.xml"));
Cache.Insert("ProductInfo", prodInfo, prodDependency);

Se avessimo creato una dipendenza su una directory, l'oggetto CacheDependency avrebbe controllato se un file viene aggiunto, modificato, cancellato o se una sotto-directory viene rinominata, creata o rimossa.

I cambiamenti che occorranno un livello inferiore a quello della subdirectory (inserimento di un file in una subdirectory o la creazione di una subdirectory in una subdirectory), non verranno notificati.

 

L'oggetto CacheDependency creato, inizierà la sua fase di controllo immediatamente, senza attendere che venga associato ad un oggetto in cache.

Più Dipendenze

Potrebbe tornare necessario un oggetto CacheDependency che dipende da più risorse (la modifica di due files decreta l'invalidità dell'oggetto in cache).

In questo caso avremo bisogno di creare un oggetto AggregateCacheDependency:

CacheDependency dep1 = new CacheDependency(Server.MapPath("ProductList1.xml"));
CacheDependency dep2 = new CacheDependency(Server.MapPath("ProductList2.xml"));
                                                                               
CacheDependency[] deps = new CacheDependency[] { dep1, dep2 };                 
                                                                               
AggregateCacheDependency aggregateDep = new AggregateCacheDependency();        
aggregateDep.Add(deps);                                                        
Cache.Insert("ProductInfo", prodInfo, aggregateDep);                           

Questo è soltanto un esempio, in quanto l'oggetto CacheDependency può accettare un array di files.
AggregateCacheDependency torna utile nel caso in cui gli oggetti da controllare non sono solamente files.

Riferendoci al post Data Caching, in questi casi torna utile l'utilizzo del delegate onRemoveCallback. Tramite questo delegate potremmo controllare perchè l'oggetto è stato rimosso, se perchè una delle sue dipendenze è cambiata o se per qualche altro motivo.

ASP.NET mette a disposizione l'enum CacheItemRemovedReason per capire perchè il nostro oggetto è stato rimosso dalla cache.

ASP.NET Caching - Data Source Controls

SqlDataSource, ObjectDataSource e XmlDataSource supportano nativamente il caching.

Usare il caching con questi controlli è altamente consigliato, perchè ad ogni postback la sorgente dati viene richiesta.
In più i dati vengono richiesti per ogni controllo in bound, quindi se avremo 3 controlli in bound, ad ogni refresh, partiranno 3 query.

In questo caso mettendo in cache questi dati si ridurranno drasticamente i tempi di caricamento.

Questi controlli offrono un insieme di proprietà utili per attivare comodamente la cache:

  • EnableCaching: true per attivare il caching.
  • CacheExpirationPolicy: può essere di due tipi Absolute o Sliding (guardare i post precedenti per capire cosa si intende)
  • CacheDuration: Se la CacheExpirationPolicy è settata su Absolute, il controllo memorizzerà i dati in cache alla prima operazione e li mantiene in memoria per il periodo di tempo specificato, quindi viene cancellata e aggiornata all'operazione successiva.
    Se invece è impostata su Sliding i dati vengono memorizzati durante la prima operazione di recupero dati, e ripristina l'intervallo di tempo durante il quale i dati vengono mantenuti nella cache per ogni operazione successiva.
    La cache scadrà se non viene registrata alcuna attività per un periodo di tempo uguale al valore settato nella CacheDuration.
  • CacheKeyDependency - SqlCacheDependency: Permette di creare una dipendenza da un oggetto in cache ad un'altro (CacheKeyDependency) o con una tabella nel database (SqlCacheDependency).

SqlDataSource

    <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
        ConnectionString="<%$ ConnectionStrings:AdventureWorksConnectionString %>" 
        EnableCaching="true"
        CacheDuration="600"
        CacheExpirationPolicy="Absolute"
        SelectCommand="SELECT * FROM Person.Contact">
        <SelectParameters>
            <asp:Parameter DefaultValue="1" Name="EmailPromotion" Type="Int32" />
        </SelectParameters>
    </asp:SqlDataSource>
        <asp:GridView ID="GridView1" runat="server" 
            DataSourceID="SqlDataSource1">
        </asp:GridView>

Per questo esempio ottengo tutti i dati dalla tabella Person.Contact del database AdventureWorks che potete trovare su CodePlex.

Test alla mano, ottenuti con Fiddler, la differenza tra i dati non copiati in cache e quelli copiati in cache è del 10% di tempo impiegato tra la request e il respond.

ObjectDataSource

L'ObjectDataSource è molto limitato, lavora solamente DataSet e/o DataTable.
Se utilizzato con un'altro tipo di oggetto riceveremo un NotSupportedException.

Se si vorranno copiare nell'ObjectDataSource cache oggetti diversi dai due sopra menzionati, bisognerà implementare anche i metodi di inserimento dei dati.

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.