wake-up-neo.net

ASP.NET MVC und IE Caching - Manipulieren von Antwortheatern ist unwirksam

Hintergrund

Ich versuche einem Kollegen zu helfen, ein Problem zu debuggen, das in den letzten 6 Monaten kein Problem war. Nach der neuesten Bereitstellung einer ASP.NET MVC 2-Anwendung haben FileResult-Antworten, die eine PDF -Datei beim Benutzer zum Öffnen oder Speichern erzwingen, Probleme, die auf dem Client-Computer lange genug für das PDF reader, um sie zu öffnen.

Frühere Versionen von IE (insbesondere 6) sind die einzigen betroffenen Browser. Firefox und Chrome sowie neuere Versionen von IE (> 8) verhalten sich alle wie erwartet. In diesem Sinne werden im nächsten Abschnitt die Aktionen definiert, die zum Wiederherstellen des Problems erforderlich sind.

Verhalten

  1. Der Benutzer klickt auf einen Link, der auf eine Aktionsmethode verweist (ein einfacher Hyperlink mit einem href-Attribut).
  2. Die Aktionsmethode generiert ein PDF, das als Byte-Stream dargestellt wird. Die Methode immer erstellt das PDF neu.
  3. In der Aktionsmethode legen Header fest, wie Browser die Antwort im Cache speichern sollen. Sie sind:

    response.AddHeader("Cache-Control", "public, must-revalidate, post-check=0, pre-check=0");
    response.AddHeader("Pragma", "no-cache");
    response.AddHeader("Expires", "0");
    

    Für diejenigen, die nicht genau wissen, was die -Header tun:

    ein. Cache-Control: public

    Gibt an, dass die Antwort möglicherweise von einem beliebigen Cache zwischengespeichert wird, auch wenn sie normalerweise nicht in einem Cache oder nur in einem nicht gemeinsam genutzten Cache gespeichert werden kann.

    b. Cache-Control: must-revalidate

    Wenn die Direktive "must-revalidate" in einer von einem Cache empfangenen Antwort vorhanden ist, DÜRF dieser Cache den Eintrag NICHT verwenden, nachdem er abgelaufen ist, um auf eine nachfolgende Anforderung Zu antworten, ohne ihn zuvor mit dem Origin-Server zu validieren

    c. Cache-Control: Pre-Check (mit IE5 eingeführt)

    Definiert ein Intervall in Sekunden, nach dem eine Entität auf Frische geprüft werden muss. Die Überprüfung kann durchgeführt werden, nachdem dem Benutzer die Ressource angezeigt wird. Er stellt jedoch sicher, dass die zwischengespeicherte Kopie bei der nächsten Roundtrip auf dem neuesten Stand ist.

    d. Cache-Control: Nachprüfung (mit IE5 eingeführt)

    Definiert ein Intervall in Sekunden, nach dem eine Entität auf Aktualität geprüft werden muss, bevor dem Benutzer die Ressource angezeigt wird.

    e. Pragma: no-cache (um die Abwärtskompatibilität mit HTTP/1.0 zu gewährleisten)

    Wenn die No-Cache-Direktive in einer Anforderungsnachricht vorhanden ist, SOLLTE eine Anwendung die Anforderung an den Origin-Server weiterleiten, auch wenn sie über eine zwischengespeicherte Kopie des Angeforderten verfügt

    f. Läuft ab

    Das Entire-Header-Feld Expires gibt das Datum und die Uhrzeit an, nach denen die Antwort als veraltet gilt.

  4. Wir geben die Datei von der Aktion zurück

    return File(file, "mime/type", fileName);
    
  5. Dem Benutzer wird ein Dialogfeld zum Öffnen/Speichern angezeigt

  6. Das Klicken auf "Speichern" funktioniert wie erwartet, aber mit "Öffnen" wird der Reader PDF gestartet. Die temporäre Datei IE wurde jedoch bereits gelöscht, wenn der Reader versucht, die Datei zu öffnen , so beschwert es sich, dass die Datei fehlt (und es ist).

Es gibt ein halbes Dutzend anderer Apps, die die gleichen Header verwenden, um Excel, CSV, PDF, Word und eine Menge anderer Inhalte bei Benutzern zu erzwingen, und es gab nie ein Problem.

Die Frage

  • Sind die Header für das, was wir versuchen, richtig? Wir möchten, dass die Datei vorübergehend vorhanden ist (zwischengespeichert wird), aber immer durch neue Versionen ersetzt werden, auch wenn die Anforderungen möglicherweise identisch sind.

Die Antwortheader werden in der Aktionsmethode festgelegt, bevor eine FileResult zurückgegeben wird. Ich habe meinen Kollegen gebeten, zu versuchen, eine neue Klasse zu erstellen, die von FileResult erbt, und stattdessen die ExecuteResult-Methode überschreiben, so dass die Kopfzeilen geändert werden und stattdessen base.ExecuteResult() ausgeführt wird - kein Status.

Ich habe eine Ahnung, dass der "Expires" -Kopf von "0" der Täter ist. Laut diesem W3C-Artikel bedeutet das Festlegen von "0", dass "bereits abgelaufen ist". Ich möchte, dass es abgelaufen ist. Ich möchte nur nicht, dass IE es aus dem Dateisystem entfernt, bevor die Anwendung, die es verarbeitet, eine Chance hat, es zu öffnen.

Wie immer danke!

Edit: Die Lösung

Bei weiteren Tests (mit Fiddler zur Überprüfung der Header) stellten wir fest, dass die Antwort-Header, von denen wir dachten, dass sie gesetzt wurden, nicht vom Browser interpretiert wurden. Da ich mit dem Code nicht vertraut war, wusste ich nichts von einem zugrunde liegenden Problem: Die Header wurden außerhalb der Aktionsmethode gestampft.

Trotzdem lasse ich diese Frage offen. Noch ausstehend ist Folgendes: Es scheint eine gewisse Diskrepanz zwischen dem Expires-Header mit dem Wert 0 und -1 zu bestehen. Wenn sich jemand auf die Unterschiede designbedingt in Bezug auf IE berufen kann, würde ich trotzdem gerne davon erfahren. Für eine Lösung funktionieren die oben genannten Header jedoch wie vorgesehen, wobei der Wert Expires in allen Browsern auf -1 gesetzt ist.

Update 1

Der Beitrag Wie kann ich das Zwischenspeichern von Webseiten in allen Browsern steuern? beschreibt ausführlich, dass das Zwischenspeichern in allen Browsern mit Hilfe von Expires = 0 verhindert werden kann. Ich bin immer noch nicht über dieses Argument 0 vs -1 verkauft ...

24
Cᴏʀʏ

Ich denke du solltest es einfach verwenden

HttpContext.Current.Response.Cache.SetMaxAge (new TimeSpan (0));

oder

HttpContext.Current.Response.Headers.Set ("Cache-Control", "private, max-age=0");

max-age=0 setzen, was nichts mehr bedeutet, da der Cache erneut validiert wird (siehe hier ). Wenn Sie zusätzlich ETag im Header mit einer benutzerdefinierten Hash-Prüfsumme aus den Daten setzen würden, wird der ETag der vorherigen Anforderung an den Server gesendet. Der Server kann entweder die Daten zurückgeben, oder, falls die Daten genau dieselben wie zuvor sind, kann er leeres Hauptteil und HttpStatusCode.NotModified als Statuscode zurückgeben. In diesem Fall erhält der Webbrowser die Daten aus dem Cache des lokalen Browsers.

Ich empfehle Ihnen, Cache-Control: private zu verwenden, um zwei wichtige Dinge zu erzwingen: 1) Deaktivieren Sie das Zwischenspeichern der Daten auf dem Proxy, der manchmal sehr aggressive Zwischenspeicherungseinstellungen hat. 2) Ermöglicht das Zwischenspeichern der Daten, erlaubt jedoch nicht die gemeinsame Nutzung des Caches andere Benutzer. Dadurch können Datenschutzprobleme gelöst werden, da die Daten, die Sie an einen Benutzer zurückgeben, möglicherweise nicht von einem anderen Benutzer gelesen werden können. Der Code HttpContext.Current.Response.Cache.SetMaxAge (new TimeSpan (0)) setzt Cache-Control: private, max-age=0 standardmäßig im HTTP-Header. Wenn Sie Cache-Control: public verwenden möchten, können Sie SetCacheability (HttpCacheability.Public); verwenden, um das Verhalten zu überschreiben, oder Headers.Set anstelle von Cache.SetMaxAge verwenden.

Wenn Sie Interesse haben, weitere Caching-Optionen des HTTP-Protokolls zu studieren, würde ich Ihnen empfehlen, das Caching-Tutorial zu lesen.

AKTUALISIERT: Ich beschließe, weitere Informationen zu schreiben, um meine Position zu löschen. Entspricht den Informationen aus der Wikipedia so alte Browser wie Mosaic 2.7, Netscape 2.0 und Internet Explorer 3.0 unterstützen den März 1996, der Vorstandard von HTTP/1.1 wird in RFC 2068 beschrieben. Ich nehme an (; aber nicht testen.) ) dass die alten Webbrowser den max-age=0 HTTP-Header unterstützen. In jedem Fall unterstützt Netscape 2.06 und Internet Explorer 4.0 definitiv HTTP 1.1.

Sie sollten Sie also zuerst fragen: Welche HTML-Standards verwenden Sie? Verwenden Sie immer noch HTML 2.0 anstelle von später im Januar 1997 veröffentlichtem HTML 3.2? Ich nehme an, Sie verwenden mindestens HTML 4.0, das im Dezember 1997 veröffentlicht wurde. Wenn Sie also Ihre Anwendung mindestens in HTML 4.0 erstellen, kann sich Ihre Website an den Web-Clients orientieren, die HTTP 1.1 unterstützen, und die Web-Clients ignorieren (nicht unterstützen) unterstützt nicht HTTP 1.1.

Nun zu anderen "Cache-Control" -Heatern wie "private, max-age = 0". Einschließlich der Header ist meiner Meinung nach pure Paranoia . Da ich selbst ein Problem mit dem Caching habe, habe ich versucht, andere Kopfzeilen einzubinden, aber nachdem ich in Abschnitt 14.9 von RFC2616 sorgfältig gelesen habe, verwende ich nur "Cache-Control: private, max-age = 0".

Der einzige "Cache-Control" -Header, der zusätzlich behandelt werden kann, ist "must-revalidate", wie in Abschnitt 14.9.4 beschrieben, auf den ich zuvor verwiesen habe. Hier ist das Zitat:

Die must-revalidate-Direktive ist erforderlich, um einen zuverlässigen - Betrieb für bestimmte Protokollfunktionen zu unterstützen. Unter allen Umständen MUSS ein HTTP/1.1-Cache die Direktive zum erneuten Validieren befolgen. insbesondere wenn der Cache den Origin-Server aus irgendeinem Grund nicht erreichen kann, MUSS er eine 504-Antwort (Gateway Timeout) generieren.

Server SOLLTEN die Muss-Revalidate-Direktive nur dann senden, wenn Die erneute Validierung einer Anforderung an die Entität zu einer fehlerhaften Operation Führen kann, z. B. eine nicht ausgeführte finanzielle Transaktion . Empfänger dürfen auf keinen Fall eine automatisierte Aktion einleiten, die Gegen diese Direktive verstößt, und sie dürfen NICHT automatisch eine Nicht validierte Kopie der Entität bereitstellen, wenn die erneute Validierung fehlschlägt.

Obwohl dies Nicht empfohlen wird, KÖNNEN Benutzeragenten, die unter schwerwiegenden Konnektivitätseinschränkungen Arbeiten, diese Direktive verletzen. Wenn dies jedoch der Fall ist, MÜSSEN Den Benutzer ausdrücklich darauf hinweisen, dass eine nicht validierte Antwort bereitgestellt wurde. Die Warnung MUSS bei jedem nicht validierten Zugriff angegeben werden und SOLLTE Eine explizite Bestätigung durch den Benutzer erfordern.

Wenn ich Probleme mit der Internetverbindung habe, sehe ich manchmal die leere Seite mit der Meldung "Gateway Timeout". Sie stammen aus der Verwendung der Direktive "must-revalidate". Ich glaube nicht, dass die "Gateway Timeout" -Meldung dem Benutzer wirklich hilft.

So sollten die Personen, die lieber selbstzerstörerische Prozeduren starten, wenn sie beim Anruf ihres Chefs das Signal "Besetzt" hören, zusätzlich die Direktive "must-revalidate" im Header "Cache-Control" verwenden. Andere Personen, die ich empfehle, verwenden einfach "Cache-Control: privat, max-age = 0" und nichts weiter.

16
Oleg

Ich weiß, dass dies zu spät ist, aber dieser Link könnte für alle Interessierten eine große Hilfe zum Thema sein: http://dotnet.dzone.com/articles/output-caching-aspnet-mvc

2
Rosdi Kasim

Für IE erinnere ich mich, dass ich Expires: -1 einstellen musste. So verhindern Sie das Caching im Internet Explorer scheint dies mit dem folgenden Code-Snippet zu bestätigen.

<% Response.CacheControl = "no-cache" %>
<% Response.AddHeader "Pragma", "no-cache" %>
<% Response.Expires = -1 %>

Im Code zurückblickend, habe ich das gefunden. Außerdem erinnere ich mich vage daran, dass sich Cache-Control: private is möglicherweise nicht korrekt mit SSL verhält.

Response.AddHeader("Cache-Control", "no-cache");
Response.AddHeader("Expires", "-1");

Also, Du willst also nicht zwischenspeichern, oder? Erwähnungen -1, verwendet jedoch stattdessen Methoden für Response.Cache:

// Stop Caching in IE
Response.Cache.SetCacheability(System.Web.HttpCacheability.NoCache);
// Stop Caching in Firefox
Response.Cache.SetNoStore();

ASP Seitenzwischenspeicherungsproblem (IE8) sagt jedoch, dass dieser Code nicht funktioniert.

0
Kevin Hakanson