1. #1
    Projektleitung
    Avatar von DMW007
    Registriert seit
    15.11.2011
    Beiträge
    4.081
    Thanked 8.506 Times in 2.538 Posts
    Blog Entries
    5

    Standard Caching Buster einer MVC-Route auf Basis einer Razor-View in ASP.NET Core

    ASP.NET Core besitzt in ihren TagHelpern einen coolen Parameter namens asp-append-version. Wenn man den auf true setzt, wird die Datei gehasht und automatisch als URL-Parameter angehangen. Dies verhindert das Cachen im Browser (gut, außer IE, aber das ist ja eigentlich auch kein Browser...) - Das ganze Arbeitet mit Caching auf Basis eines FileWatchers, also in Echtzeit und extrem performant. Für JS Dateien im Dateisystem super, da nutze ich das standardmäßig.

    Genau das will ich haben, aber für eine dynamisch generierte Razor-View. Die Ansicht gibt JavaScript-Code zurück, der dynamisch generierte Konfigurationsparameter enthält. Die Action dazu sieht wie folgt aus:

    public IActionResult Config() {
    Response.ContentType = "text/javascript";
    string ExpireDate = DateTime.UtcNow.AddMonths(2).ToString("ddd, dd MMM yyyy HH:mm:ss", CultureInfo.InvariantCulture);
    Response.Headers.Add("Expires", $"{ExpireDate} GMT");
    return View("~/Views/Home/Config.cshtml");
    }


    In .NET Core 2.2 ist eine Erweiterung AddFileVersionToPath() enthalten, die quasi das asp-append-version Attribut auf Dateien direkt anwendet. Sprich ohne den TagHelper. Da ich die LTS (2.1) verwende und das schon für einen Editor gebraucht habe, der via JS-Variable ein Stylesheet nachlädt, habe ich folgendes Polyfill:

    public static class RazorExtensions {
    /// <summary>
    /// Gets the path to a file in wwwroot with version hash for cache management
    /// </summary>
    public static string AddFileVersionToPath(this IRazorPage page, string path) {
    var context = page.ViewContext.HttpContext;
    IMemoryCache cache = context.RequestServices.GetRequiredService<IMemoryCache>();
    var hostingEnvironment = context.RequestServices.GetRequiredService<IHostingEnvironment>();
    var versionProvider = new FileVersionProvider(hostingEnvironment.WebRootFileProvider, cache, context.Request.Path);
    return versionProvider.AddFileVersionToPath(path);
    }
    }

    Problem ist, dass dabei meine URLs kaputt gehen. Ich habe eben ja nicht einen einzigen Pfad, der im wwwroot liegt und an den er einfach seinen Cache-Buster anhängen soll wie /css/foo.css. Sondern er soll /Views/Home/Config.cshtml.js als Grundlage für den Hash nutzen und diesen dann aber an die Route /home/config anhängen.

    @{
    string configPath = this.AddFileVersionToPath("../Views/Home/Config.cshtml");
    }

    Da er als Basis den WebRoot nutzt, müsste das logisch richtig sein. Er ermittelt aber nicht mal den Hash. Und wie schon eben erwähnt, generiert er mir einen JS Tag mit ../Views/Home/Config.cshtml.js der so natürlich auch nicht als Route existiert.

    Jemand eine Idee, wie man das fix lösen kann?
    Spontan fällt mir da nichts ein außer eben den FileVersionProvider genauer anzuschauen und den ein Stück weit angepasst nachzubauen, sodass zwischen zu hashender Datei und der Zieldatei unterschieden werden kann.


  2. #2
    Projektleitung
    Avatar von DMW007
    Registriert seit
    15.11.2011
    Beiträge
    4.081
    Thanked 8.506 Times in 2.538 Posts
    Blog Entries
    5

    Standard AW: Caching Buster einer MVC-Route auf Basis einer Razor-View in ASP.NET Core

    Hab mir selbst eine entsprechende Erweiterung gebastelt:

    public static class RazorExtensions {
    /// <summary>
    /// Gets the path to a virtual file (e.g. MVC route) with version hash, where the hash is calculated from a dynamic Razor file. Caches the hash using a file watcher.
    /// IMPORTANT: To make this work, set <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> for the affected view to avoid missing file exceptions when deploying to prod server without VS dev env
    /// </summary>
    /// <param name="physicalRazorFilePath">Path to an existing file relative to the project root dir (e.g. /Views/Dummy/SomeView.cshtml)</param>
    /// <param name="virtualWebPath">Route where the file would be accessed (e.g. /Dummy/SomeView) - This is used as base for the caching parameter</param>
    /// <returns></returns>
    public static string AddFileVersionToVirtualPath(this IRazorPage page, string physicalRazorFilePath, string virtualWebPath, string noCacheKey = "v") {
    var context = page.ViewContext.HttpContext;
    IMemoryCache cache = context.RequestServices.GetRequiredService<IMemoryCache>();
    string cacheKey = physicalRazorFilePath + virtualWebPath;
    if(cache.TryGetValue(cacheKey, out string value)) {
    return value;
    }

    var hostingEnvironment = context.RequestServices.GetRequiredService<IHostingEnvironment>();
    var fileInfo = hostingEnvironment.ContentRootFileProvider.GetFileInfo(physicalRazorFilePath);

    string hash = "";
    using (var stream = fileInfo.CreateReadStream()) {
    var ms = new MemoryStream();
    stream.CopyTo(ms);
    byte[] data = ms.ToArray();
    var algorithm = SHA256.Create();

    var sbHash = new StringBuilder();
    byte[] rawHash = algorithm.ComputeHash(data);
    foreach(byte b in rawHash) {
    sbHash.Append(b.ToString("x2"));
    }
    hash = sbHash.ToString();
    }

    string fullPath = QueryHelpers.AddQueryString(virtualWebPath, noCacheKey, hash);
    var token = hostingEnvironment.ContentRootFileProvider.Watch(physicalRazorFilePath);
    var cacheEntryOptions = new MemoryCacheEntryOptions().AddExpirationToken(token);
    cache.Set(cacheKey, fullPath, cacheEntryOptions);

    return fullPath;
    }
    }

    Beispiel:

    @{
    string configPath = this.AddFileVersionToVirtualPath("/Views/Home/Config.cshtml", "/home/config");
    }
    <script src="@configPath"></script>

    Ergibt folgendes gerendertes HTML:
    HTML-Code:
    <script src="/home/config?v=783d8bfbcf1a33ad0c46b40fbe456350372b2e0cc0b72b2201353445b01016e4"></script>
    Wie beim TagHelper von .NET Core wird die generierte URL mit Hash bei der ersten Anfrage im RAM-Cache des Servers abgelegt. Es erfolgt also kein ständies Neu-Hashen bei jeder Anfrage.


Ähnliche Themen

  1. [Vorstellung] einer mehr im Gewimmel
    Von Babaji im Forum Userankündigungen
    Antworten: 6
    Letzter Beitrag: 27.06.2013, 17:31
  2. Kauf einer xBox360
    Von Kolle1991 im Forum Microsoft Xbox
    Antworten: 21
    Letzter Beitrag: 16.04.2012, 14:57
  3. Was in einer Internetminute passiert
    Von DMW007 im Forum Internet und Technik
    Antworten: 3
    Letzter Beitrag: 03.04.2012, 01:55
  4. Wertfrage einer Wii
    Von Young Jeezy im Forum Nintendo
    Antworten: 7
    Letzter Beitrag: 25.02.2012, 23:13
Diese Seite nutzt Cookies, um das Nutzererlebnis zu verbessern. Klicken Sie hier, um das Cookie-Tracking zu deaktivieren.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191