Danny van der Kraan
Danny van der Kraan Software Engineer
| 4 reactie(s)

ASP.NET Core 1.0: Static Files Caching en Cache-Busting

Caching

Wanneer je web applicaties ontwikkelt is een van de eerste dingen die je wil doen om de performance te verbeteren de statische bestanden [1], zoals afbeeldingen en script bestanden, te cachen en ze enkel ophalen van de server wanneer deze bestanden ook daadwerkelijk gemodificeerd zijn.

Er zijn een aantal situaties waarin een browser checkt of de bestanden in de cache niet valide zijn[2]:

  • De bestanden in de cache hebben geen expiratie datum en de inhoud wordt voor het eerst benaderd in de browsersessie
  • De bestanden in de cache hebben wel een expiratie datum en die ligt in het verleden
  • De eindgebruiker heeft een verversing van de pagina gevraagd door de ververs knop te klikken of via F5

Kanttekening: Wanneer je dus zelf het cachen van je statische bestanden gaat testen, let dan even op het laatste punt. Maak een link die terugverwijst naar de pagina zelf in plaats van verversen (of F5 drukken), want het cachen zal dan niet werken.

Zoals je kon lezen in de eerste twee situaties kunnen statische bestanden een expiratie datum meegegeven worden. Dit was feitelijk een datum en tijd stempel in de http header, maar tegenwoordig wordt de “max-age” van de “cache-control” HTTP header veel gebruikt, want dit stelt je in staat een interval te gebruiken [3]. Hierdoor hoef je geen expliciete datum meer op te geven. Een interval is veel flexibeler. Daarbij wordt “max-age” geprefereerd door moderne browsers. Dus laten we voor dit attribuut gaan!

Kanttekening: Volgens specificaties zou je het “max-age” attribuut nooit op meer dan een jaar moeten zetten.

Om de “max-age” uberhaupt te kunnen zetten in de “cache-control” header zal je eerst de Middleware voor Static Files moeten toevoegen. Middleware verdient zijn eigen artikel en is niet de focus van deze. In een ASP.NET Core 1.0 applicatie doe je dit door de volgende code in de Configure methode van de Startup class op te nemen:

Listing 1: Voeg Static Files Middleware aan de ‘HTTP request pipeline’ toe

Hierna kan je de “max-age” in de ‘response headers’ wijzigen. De UseStaticFiles methode accepteert namelijk een instantie van de class StaticFileOptions. Deze class heeft een OnPrepareResponse property [4] welk je kan gebruiken om zaken toe te voegen of te wijzigen in de ‘ response headers’. Deze property verwacht een methode met een enkele parameter en die geen waarde retourneert. Zo kunnen we deze property dus als volgt gebruiken om de “max-age” te wijzigen:

Listing 2: De ‘response headers’ wijzigen via OnPrepareResponse

Zoals te zien is in het code voorbeeld verwacht MaxAge een TimeSpan wat het erg makkelijk maakt om een interval op te geven. Voor test doeleinden heb ik het in dit voorbeeld even op 60 seconden gezet. Voor de operationele omgeving is het waarschijnlijk dat je deze waarde op of tegen een jaar aan wil zetten. We kunnen bijvoorbeeld in de ‘developer tools’ van Chrome checken of de response headers er uit zien zoals we nu verwachten:

200OKfromcache
Figuur 1: max-age = 60 en Status Code = 200 OK (from cache)

Door de ‘headers’ te inspecteren kan ik inderdaad zien dat “max-age” inderdaad op 60 seconden is gezet in de “Cache-Contol” ‘response header’ en wanneer ik de pagina opnieuw ophaal, door op een link te klikken die naar de pagine verwijst waar de link op staat (geen verversing of F5), zie ik dat de status code “200 OK (from cache)” is. Wanneer ik 60 seconden wacht en daarna weer op diezelfde link klik zie ik status code “304 Not Modified” . Perfect, de tijdsinterval is vervallen en de browser checkt met een hele kleine ‘request’ of het bestand gewijzigd is. De server beantwoord met een evenzo kleine ‘response’ terug “304 Not Modified” bewerend dat het verzochte bestand niet gewijzigd is.

304NotModified
Figuur 2: Status Code = 304 Not Modified

En dus werd het grote .jpg bestand niet nog een keer over het lijntje getrokken door de browser. Dit begint er op te lijken, niet? Maar wat doen we dan met bestanden die binnen de interval zijn gewijzigd? Het wordt tijd om te kijken naar “Cache-busting”:

Cache-busting

“Cache-busting” betekent kort gezegd voorkomen dat de browser een statisch bestand van zijn cache aanbiedt [5]. MVC 6 (waar we nu weer aan moeten refereren als ASP.NET Core 1.0) heeft een TagHelper [6] genaamd “asp-append-version” welk gebruikt kan worden bij alle HTML tags die te maken hebben met statische bestanden, zoals bijvoorbeeld de “img” tag. Wat deze TagHelper feitelijk doet wanneer je diens waarde op ‘true’ zet is gebruik maken van een hele bekende manier van “cache-busting” forceren. Namelijk door statische bestanden versies meegeven. De ‘Request URL’ wordt uitgebreid als volgt: “?v=o90nYT80kXYsi_vP1o1mIgqmQzrL3QV-ADZlxN_M9_8”. Dit is gewoon een ‘query string’ (zie de ?) met de parameter v (wat voor ‘version’ staat uiteraard) en een hash code achter het gelijk aan teken (=) welk berekend is door de server op basis van de inhoud van het statisch bestand die aangeboden wordt. Dus bijvoorbeeld:

Listing 3: Image tag met asp-append-version TagHelper

De browser zal het statisch bestand cachen met deze gemodificeerde ‘request URL’ als referentie. Wanneer de inhoud van het bestand wijzigt zal de server een nieuwe hash code berekenen op basis van de nieuwe inhoud en alles wat de browser ‘weet’ is dat de URL daadwerkelijk veranderd is, waardoor de browser ‘denkt’ dat het dit statisch bestand opnieuw moet ophalen van de server in plaats van uit z’n cache. Laten we dit even bekijken in de ‘developer tools’ van Chrome:

200OKFromCache_2
Figuur 3: Status Code = 200 OK (from cache) wanneer statisch bestand een versie heeft

Als we midden in de interval van “max-age” de eerdere genoemde link klikken dan zien we status code “200 OK”, maar “(from cache)” dit keer! Vestig je aandacht even op de “ Request URL” in figuur 3, daar zie je het geven van een versie door de server in actie.

Nadat de interval van “max-age” is vervallen gedraagt de browser zich zoals verwacht en moet de server vragen of er iets gewijzigd is:
304NotModified_2
Figuur 4: Status Code = 304 Not Modified wanneer statisch bestand een versie heeft

Maar wat als de “max-age” interval nog niet vervallen is en het statisch bestand is wel gewijzigd. In dit voorbeeld zou dat betekenen dat de afbeelding gemuteerd is. Zal ik je laten zien:
200OK
Figuur 5: Status Code = 200 OK wanneer statisch bestand een versie heeft

Vergelijk de ‘ Request URL’ van bovenstaande screenshot alstublieft met de screenshot van figuur 4. Merk op dat de hash code compleet verschillend is. De browser ‘vergat’ z’n cache en ging naar de server om een ‘nieuwe’ statisch bestand op te halen, omdat voor browser de link compleet anders is.

Kanttekening: Ik kreeg dus bovenstaand beschreven resultaat door de veelvuldig genoemde link weer te klikken en eerst er zeker van te zijn dat ik een status code “200 OK (from cache)” kreeg. Daarna opende ik het plaatje in Paint. Ik voegde er een lijntje aan toe. Ik sloeg het plaatje op. Daarna sloot ik het programma. Vervolgens klikte ik de link weer. Extra advies als je dit zelf gaat testen, maak van “ max-age” een uur of iets dergelijks in plaats van 60 seconden.

Conclusie

Het gemak waarmee je in de ‘HTTP request pipeline’ kan ingrijpen met ASP.NET Core 1.0 en ‘response headers’ kan toevoegen/wijzigen is heel fijn. Het stelt je in staat om bijvoorbeeld de “max-age” met minimale inspanning te wijzigen en zo de browser’s inherente mogelijkheid te gebruiken om statische bestanden te cachen. En om vervolgens via de TagHelper “asp-append-version” weer met het grootste gemak “cache-busting” toe te kunnen passen als een statisch bestand gewijzigd wordt is zeer aangenaam. Eenmaal opgesteld hoef je als developer je hier dus niet meer druk over te maken op dit niveau van detail en kan je concentreren op het bouwen van geweldige web applicaties voor je klanten.

Bronnen

  1. Static files: https://docs.asp.net/en/latest/fundamentals/static-files.html
  2. HTTP en ‘caching’: http://www.httpwatch.com/httpgallery/caching/
  3. HTTP cache headers: http://www.mobify.com/blog/beginners-guide-to-http-cache-headers/
  4. OnPrepareResponse: https://msdn.microsoft.com/en-us/library/microsoft.owin.staticfiles.staticfileoptions.onprepareresponse
  5. Definitie “cache-busting”: http://digitalmarketing-glossary.com/What-is-Cache-busting-definition
  6. TagHelpers: https://docs.asp.net/projects/mvc/en/latest/views/tag-helpers/index.html

Reacties (4)

  1. Richard Nobel schreef:

    Cache-busters i.p.v. Ghostbusters 😉

    Goed artikel, Danny! Alleen in plaats van “De ‘Request URL’ wordt geaugmenteerd…” zou ik eerder schrijven “…wordt aangevuld/uitgebreid met…”. Maar dat kan aan mij liggen: ik heb iets met taal. Niet alléén programmeertalen 🙂

    Hoe dan ook. Als collega-webontwikkelaar ga ik ook maar weer eens in m’n projecten nalopen, of er geen statische bestanden onnodig (herhaaldelijk) worden opgehaald. Dank voor de ‘reminder’.

    Overigens ben ik van mening dat alle web-ontwikkelaars zich wel enigszins zouden moeten verdiepen in het HTTP protocol. En o.a. de verschillen tussen “normale” requests en XMLHttpRequest/AJAX (dan wil je meestal geen cache). Plus daarbij inderdaad kijken wat er – aan headers – over de lijn gaat. Helemaal nu dat zo eenvoudig toegankelijk is dankzij de “F12-tools” in diverse browers! 😀

    1. Hey Richard,

      Bedankt voor je reactie(s)! Top!

      Ik ben het er eigenlijk wel mee eens dus ik heb gevraagd of ze er “uitgebreid” van kunnen maken. Komt denk ik omdat oorspronkelijk m’n blogpost in ’t engels was.

      Er valt inderdaad enorme performance winst te behalen met het beheersen van de HTTP protocol. Ik zal eens kijken of er nog meer artikeltjes in de koker zitten hierover.

      Succes met het nalopen van je projecten. 🙂

    2. Bedankt voor de tips Richard. Op verzoek van Danny is het zojuist aangepast!

      1. Richard Nobel schreef:

        Goed bezig 🙂 Dit wordt zo een leuke verzameling blog-artikelen. 📚

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Verplichte velden zijn gemarkeerd met *

Meer weten over onze werkwijze en dienstverlening? Neem gerust contact met ons op!