Response Caching funktioniert nicht mit ASP.NET core

Wieder einmal in einer ASP.NET core Schulung stellen mich die Teilnehmer vor eine Herausforderung. Thema ist das Response Caching. In ASP.NET Webforms lautet die analoge Funktion Output Caching und ist grenzgenial gelöst. Einfach ganz oben in der ASPX Seite einfügen und Performance Boost erleben.

<%@ OutputCache Duration="90" VaryByParam="*" VaryByCustom="browser" %>

Das schöne daran, der gerenderte HTML Code am Server wird für hier 90 Sekunden vorgehalten. Nicht nur das der Rendering Aufwand entfällt, auch darin enthaltene Datenbank Zugriffe fallen weg.

Was in ASP.NET Webforms schön und richtig war, soll doch auch in ASP.NET Core einfach zu lösen sein.

In der Regel demonstriere ich den aspnetcore Schulungsteilnehmern das partielle Caching, z.b. auf Razor Partial Ebene mit dem Caching Taghelper.

   1:  <cache enabled="true" expires-after="@new TimeSpan(0,0,10)" vary-by-query="id" >
   2:      @DateTime.Now
   3:  </cache>

Damit wird ein Teil des gerenderten HMTL Codes zwischengespeichert. Was ist aber mit der ganzen Page? Dazu fügt man einfach der PageModel Klasse einer Razor View ein passendes Attribut hinzu.

Im Web finden sich Stellen, die beschreiben, das man das Nuget Paket Microsoft.AspNetCore.ResponseCaching installieren müsse. Das ist in meinem Fall nicht so. Das Response Cache Attribut findet sich im Namensraum using Microsoft.AspNetCore.Mvc.

Allerding muss in der Datei startup.cs die Middleware für Response Caching eingefügt (add) und konfiguriert (use) werden.

   1:  public void ConfigureServices(IServiceCollection services)
   2:  {                
   3:  services.AddResponseCaching();
   4:   
   5:  ...
   6:   public void Configure(IApplicationBuilder app, IHostingEnvironment env)
   7:  {
   8:  app.UseResponseCaching();      

Wenn das nicht geschehen ist, klappt das Caching nicht, aber es gibt auch keine Fehlermeldung, wenn man das Attribut in der Razor Pagemodel Klasse gesetzt ist.

   1:  [ResponseCache(Duration =300,Location =ResponseCacheLocation.Any)]
   2:      public class cachedModel : PageModel

Dieses Attribut lässt sich auch in einem ASP.NET MVC Controller oder einer ASP.NET Web Api einsetzen. Jedenfalls sollte dann bei mehrmaligen Request nicht mehr die OnGet Methode angesprungen werden. Wird es aber in meinem C# Code. Das ASP.NET core Response Caching funktioniert einfach nicht. Keine Fehlermeldung, einfach kein Caching der Page.

Die Liste mit Bedingungen die Erfüllt sein müssen, ist auf der Microsoft Doku dazu 13 Punkte lang. Die Untersuchung des Response und Request in den Chrome F12 Browser Tools zeigt, das der Browser möchte das der Inhalt nicht gecached wird. Cache-Control no-Cache im HTTP Header.

image

Per Test in Postman oder Fiddlertool wird der Request Header manipuliert und die beiden Einträge (auch pragma) entfernt und  voi·là Caching funktioniert!

Die Microsoft Entwickler stellen also das Browser Bedürfnis über den des Web Developers am Server. Das mag irgendwelchen Standards entsprechen, ist unerwartet und auch intransparent.

Nun ist dotnet core ja Open Source und ich habe mich darin umgesehen. Also schreibe ich meinen eigenen Custom ResponseCaching Provider, indem man den Provider erbt und die beiden Methoden überschreibt die den Header checken. Ein bayrisches “passt scho” oder true als Rückgabe.

   1:  public class ResponseCachingForced: ResponseCachingPolicyProvider
   2:  {
   3:   public override bool AllowCacheLookup(ResponseCachingContext context)
   4:    {
   5:              return true;
   6:     }
   7:   public override bool AllowCacheStorage(ResponseCachingContext context)
   8:    {
   9:          return true;
  10:     }
  11:  }

Um das ganze elegant abzuschließen, wird noch ein wenig Code geklaut, um eine Add Extension Methode für die IServiceCollection zu bauen. Die einzige Änderung, in Zeile 5 hinten, den “forced” Response Caching Provider einsetzen.

   1:  public static class ResponseCachingServicesExtensions
   2:  {
   3:   public static IServiceCollection AddResponseCachingForced(this IServiceCollection services)
   4:     {
   5:      services.TryAdd(ServiceDescriptor.Singleton<IResponseCachingPolicyProvider, ResponseCachingForced>());
   6:      services.TryAdd(ServiceDescriptor.Singleton<IResponseCachingKeyProvider, ResponseCachingKeyProvider>());
   7:      return services;
   8:    }
   9:         
  10:  public static IServiceCollection AddResponseCaching(this IServiceCollection services, Action<ResponseCachingOptions> configureOptions)
  11:   {
  12:      services.Configure(configureOptions);
  13:      services.AddResponseCaching();
  14:      return services;
  15:    }
  16:  }

Nun muss nur noch in Startup.cs in der DI Service Collection der Austausch der Cachingprovider stattfinden.

   1:  // services.AddResponseCaching();
   2:  services.AddResponseCachingForced();

So funktioniert es dann auch mit Response Page Caching in aspnet core.

Kommentare sind geschlossen