Nello script precedente (https://www.aspitalia.com/script/1285/Aumentare-Scalabilita-ASP.NET-Core-Web-API-Caching-Client-Side.aspx) abbiamo visto come il caching client side sia uno dei modi più efficaci per aumentare la scalabilità del nostro strato di servizi: quando è possibile, i client possono mantenere una cache locale delle risposte così da evitare del tutto la chiamata al server.
Il problema fondamentale di questo approccio, tuttavia, è che perdiamo il controllo di quanto memorizzato sul client, fintanto che non scada il TTL che abbiamo settato tramite l'header cache-control: se i dati dovessero cambiare, infatti, non abbiamo infatti un sistema per invalidare le cache dei client dal server. Quindi per forza di cose il TTL è tipicamente piuttosto basso, in modo da essere efficace per chiamate ripetute, ma comunque garantire un ragionevole refresh rate delle risposte. Questo fa sì che comunque i client, a intervalli regolari, invalideranno la loro cache, chiameranno il server e, pertanto, consumeremo banda.
Un sistema molto utile per mitigare questo problema è l'utilizzo di ETag, uno standard HTTP il cui principio di funzionamento è descritto nell'immagine in basso.
L'ETag è un header della risposta HTTP che contiene tipicamente un identificativo o un hash. Esso viene generato dal server e mantenuto in cache dal client. Quando il client effettuerà nuovamente la richiesta, invierà in allegato l'ETag all'interno dell'header If-None-Match. Se il valore corrisponde alla versione corrente sul server, quest'ultimo ritornerà una risposta di tipo 304 - Not Modified, che non contiene alcun body (e pertanto è molto leggera e veloce) e che istruirà il client a mantenere i dati precedenti in cache per un ulteriore TTL.
ASP.NET Core Web API non supporta direttamente questa funzionalità, ma possiamo implementarla facilmente grazie a un Action Filter come il seguente:
public class EnableETagAttribute : Attribute, IActionFilter { public void OnActionExecuted(ActionExecutedContext context) { var clientEtag = context.HttpContext.Request.Headers["If-None-Match"].FirstOrDefault(); var result = context.Result as ObjectResult; if (result == null) return; var serverEtag = JsonConvert.SerializeObject(result.Value).GetHashCode().ToString(); if (serverEtag == clientEtag) { context.Result = new StatusCodeResult(304); } context.HttpContext.Response.Headers.Add("Etag", serverEtag); } public void OnActionExecuting(ActionExecutingContext context) { } }
Questo filtro usa come ETag l'hash .NET della risposta, ma possiamo utilizzare qualsiasi logica per estrarre una stringa che sia univocamente verificabile. Come prima cosa, recuperiamo il contenuto dell'header If-None-Match dalla request e successivamente, calcoliamo l'hash di quanto restituito dalla Action. Se i due valori corrispondono, modifichiamo la risposta del server in un 304. Il valore dell'ETag è poi in ogni caso ritornato come header.
La peculiarità di questo filtro è che funziona con qualsiasi Action o Controller di Web API: per utilizzarlo, infatti, è sufficiente aggiungerlo, avendo cura di impostare anche il caching client side come abbiamo visto nel precedente script:
[HttpGet, EnableETag, ResponseCache(Location = ResponseCacheLocation.Any, Duration = 30)] public async Task<IEnumerable<string>> Get() { // .. altro codice qui .. }
Un aspetto importante da sottolineare, tuttavia, è che l'action viene comunque eseguita in ogni caso. Quindi questa tecnica ottimizza l'utilizzo di banda, ma non il carico lato server. Una possibile alternativa, potrebbe essere quella di mantenere uno storage in cache degli ETag validi, così da evitare interamente il calcolo della risposta. Ovviamente una soluzione di questo tipo non è però sempre attuabile e, soprattutto, va tipicamente gestita caso per caso nella logica della action stessa, invece che con un filtro esterno.
Commenti
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
Aggiungere interattività lato server in Blazor 8
Sviluppare un'interfaccia utente in React con Tailwind CSS e Preline UI
Ottimizzare le performance delle collection con le classi FrozenSet e FrozenDictionary
Potenziare Azure AI Search con la ricerca vettoriale
Registrare servizi multipli tramite chiavi in ASP.NET Core 8
Installare le Web App site extension tramite una pipeline di Azure DevOps
Utilizzare EF.Constant per evitare la parametrizzazione di query SQL
Utilizzare un numero per gestire la concorrenza ottimistica con SQL Server ed Entity Framework
Usare le collection expression per inizializzare una lista di oggetti in C#
Come EF 8 ha ottimizzato le query che usano il metodo Contains
Migliorare i tempi di risposta di GPT tramite lo streaming endpoint in ASP.NET Core
Criptare la comunicazione con mTLS in Azure Container Apps
I più letti di oggi
- Effettuare il log delle chiamate a function di GPT in ASP.NET Web API
- ecco tutte le novità pubblicate sui nostri siti questa settimana: https://aspit.co/wkly buon week-end!
- Utilizzare il metodo CountBy di LINQ per semplificare raggruppamenti e i conteggi
- Creare una libreria CSS universale: Cards
- Eseguire script pre e post esecuzione di un workflow di GitHub