Cache Busting in ASP.NET Core

Es ist weit verbreitet, ja sogar eigentlich normal, statische Ressourcen clientseitig zu speichern um den Datenverkehr zu reduzieren und damit unter anderem die Zeit, welche zum Laden einer Webseite benötigt wird, sowie die Serverauslastung so tief wie möglich zu halten. Es ist nicht unüblich, Daten wie Bilder, Stylesheets und Javascript-Dateien ein Jahr lang zu cachen.

 

Das Problem, welches sich für Entwickler damit natürlich stellt ist, wie man nun etwas wieder aus dem Cache hinausbringt. Dies ist insbesondere dann notwendig, wenn die gecachten Dateien verändert wurden, und sich die Änderungen so schnell wie möglich auf alle Nutzer auswirken sollen. Es wäre äusserst problematisch, wenn sich diese Updates erst rund ein Jahr später zeigen würden! Leider ist der clientseitige Browser-Cache jedoch für eine Webapplikation unerreichbar, weshalb zu anderen Lösungen gegriffen werden muss. Im folgenden wird eine Lösung präsentiert, welche mit ASP.NET Core 2 MVC-Applikationen funktioniert. Eine Adaption für andere Technologien (zum Beispiel .NET oder Angular) ist sicherlich möglich.

 

Die Anforderungen, die an die Lösung gestellt wurden, waren, dass sie es nicht erfordert, die Dateien, welche ein Update erhalten haben, bei jeder Änderung umbenannt werden müssen (was bei vielen anderen Lösungen der Fall ist). Weiter sollen diese Dateien nicht einfach nicht mehr gecacht werden, insbesondere, da die meisten der in Frage stehenden Daten im Falle der Applikation, für welche diese Lösung entwickelt wurde, sich nur selten ändern.

 

Die Grundidee besteht darin, den Browser mit einem Trick glauben zu lassen, dass nach einer Änderung eine andere Datei als zuvor gebraucht wird. Denn wenn der Browser entscheidet, ob er Daten aus dem Cache oder vom Server beziehen soll schaut dieser lediglich auf den Pfad der Ressourcenanfrage, nicht auf den Inhalt der Datei. Bestandteil einer URL sind die optionalen Query-Parameter, welche mit dem "?"-Symbol an den Hauptbestandteil der URL angefügt werden. Diese werden beim Laden von Ressourcen in der Regel nicht benötigt, und selbst wenn diese benötigt werden, funktioniert dieser Trick. Denn, wenn sich die Query-Parameter von zwei Anfragen unterscheiden wird der Browser die entsprechende Datei nicht aus dem Cache laden sondern vom Server anfordern. Die Idee ist also, der Ressourcenanfrage einen Query-Parameter anzufügen (welcher das Finden der Ressource auf dem Server nicht weiter beeinträchtigt), und diesen jedesmal, wenn sich die Ressource ändert, zu ändern. Dieser Parameter sollte möglichst eindeutig sein. Es bietet sich an, das Datum der letzten Änderung der Datei zu verwenden, denn ein solches Datum inklusive genauer Zeit ist genau genug, um (pro Datei) eindeutig zu sein, und es ist ein Datensatz, welcher nicht zusätzlich generiert und permanent abgespeichert werden muss.

 

Für den Fall von Bildern (welche auf dem File-System abgelegt sind) müssen nur wenige Änderungen vorgenommen werden:

Änderungen am Backend

Am Backend muss das Änderungsdatum des Bildes, welches sich bei Änderung aktualisieren soll ausgelesen werden, und an das Frontend übergeben werden. Für das Auslesen des Änderungsdatums kann die statische Methode GetLastWriteTime() der System.IO.File-Klasse verwendet werden. Diese benötigt den absoluten Speicherpfad des Bildes. In ASP.NET Core 2 ist es etwas umständlich, an den absoluten Teil des Pfades, also den Pfad bis hin zum wwwroot-Ordner zu kommen. Dazu muss im Konstruktor des Controllers, welche die Action enthält, die die View zurückgibt, die das betroffene Bild enthält IHostingEnvironment über Dependency Injection geholt werden. Die Methode WebRootPath() dieser IHostingEnvironment-Instanz liefert dann den Pfad bis zum wwwroot-Folder. Zu diesem Pfad werden dann die relativen Pfad-Anteile konkateniert. 

 

Das erhaltene Datum muss dann an die View übergeben werden. Dies kann zum Beispiel über ViewData oder ViewBag geschehen. Wenn das Bild mit einem Datensatz, welcher über ein ViewModel übergeben wird, assoziiert ist, ist es sicherlich auch eine schöne Lösung, das Änderungsdatum im ViewModel abzulegen.

Änderungen am Frontend

Am Frontend muss lediglich der Pfad zum Bild, welcher im entsprechenden <img>-Tag um den erwähnten Query-Parameter erweitert werden. Es spielt keine Rolle, wie dieser Query-Tag genau aussieht, solange er eindeutig ist genügt es, den Browser dazu zu bringen, die Datei vom Server neu anzufordern. Ein mögliches Pattern wäre es zum Beispiel, als Parameter-Namen "v" (für "Version") zu verwenden, und das Änderungsdatum in die kleinstmögliche Zeiteinheit umzuwandeln, so dass es nur noch aus Zahlen besteht. Damit werden allfällige Nebeneffekte durch Sonderzeichen im Datumsformat vermieden.