web-dev-qa-db-fra.com

ASP.NET MVC et IE mise en cache - la manipulation des en-têtes de réponse est inefficace

Contexte

J'essaie d'aider un collègue à résoudre un problème qui ne l'a pas été au cours des six derniers mois. Après le déploiement le plus récent d'une application ASP.NET MVC 2, les réponses FileResult qui forcent un fichier PDF à s'ouvrir ou à enregistrer à l'utilisateur rencontrent des problèmes qui persistent suffisamment longtemps sur la machine cliente pour le PDF lecteur pour les ouvrir.

Les versions antérieures de IE (notamment 6) sont les seuls navigateurs concernés. Firefox et Chrome et les versions plus récentes de IE (> 8) se comportent tous comme prévu. Dans cet esprit, la section suivante définit les actions nécessaires pour recréer le problème.

Comportement

  1. L'utilisateur clique sur un lien qui pointe vers une méthode d'action (un hyperlien simple avec un attribut href).
  2. La méthode action génère un PDF représenté sous forme de flux d'octets. La méthode toujours recrée le PDF.
  3. Dans la méthode d'action, les en-têtes sont configurés pour indiquer aux navigateurs comment mettre en cache la réponse. Elles sont:

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

    Pour ceux qui ne connaissent pas exactement ce que font les en-têtes :

    une. Cache-Control: public

    Indique que la réponse PEUT être mise en cache par n'importe quel cache, même si elle ne peut normalement pas être cachée ou ne peut être cachée que dans un cache non partagé.

    b. Cache-Control: must-revalidate

    Lorsque la directive must-revalidate est présente dans une réponse reçue par un cache, celui-ci NE DOIT PAS utiliser l'entrée une fois qu'il devient obsolète pour répondre à une demande ultérieure de Sans d'abord le revalider avec le serveur d'origine.

    c. Cache-Control: contrôle préalable (introduit avec IE5)

    Définit un intervalle en secondes après lequel une entité doit être vérifiée pour sa fraîcheur. La vérification peut avoir lieu après que l'utilisateur a montré la ressource, mais elle garantit que lors de la prochaine tournée, la copie en cache sera à jour.

    ré. Cache-Control: post-check (introduit avec IE5)

    Définit un intervalle en secondes après lequel une entité doit être vérifiée avant d’afficher la ressource à l’utilisateur.

    e. Pragma: no-cache (pour assurer la compatibilité ascendante avec HTTP/1.0)

    Lorsque la directive no-cache est présente dans un message de demande, une application DEVRAIT transférer la demande vers le serveur d'origine même si elle dispose d'une copie en cache de ce qui est demandé.

    f. Expire

    Le champ d'en-tête d'entité Expires donne la date/heure après laquelle la réponse est considérée comme périmée.

  4. Nous retournons le fichier de l'action

    return File(file, "mime/type", fileName);
    
  5. Une boîte de dialogue Ouvrir/Enregistrer est présentée à l'utilisateur.

  6. Un clic sur "Enregistrer" fonctionne comme prévu, mais un clic sur "Ouvrir" lance le lecteur PDF, mais le fichier temporaire IE enregistré a déjà été supprimé au moment où le lecteur tente d'ouvrir le fichier. , donc il se plaint que le fichier est manquant (et il est).

Une demi-douzaine d'autres applications utilisent les mêmes en-têtes pour forcer les utilisateurs à utiliser Excel, CSV, PDF, Word et une tonne d'autres contenus. Il n'y a jamais eu de problème.

La question

  • Les en-têtes sont-ils corrects pour ce que nous essayons de faire? Nous voulons que le fichier existe temporairement (soit mis en cache), mais qu'il soit toujours remplacé par de nouvelles versions, même si les demandes peuvent être identiques).

Les en-têtes de réponse sont définis dans la méthode d'action avant de renvoyer FileResult. J'ai demandé à mon collègue d'essayer de créer une nouvelle classe qui hérite de FileResult et de remplacer la méthode ExecuteResult pour qu'elle modifie les en-têtes, puis que base.ExecuteResult() soit remplacé - aucun statut à ce sujet.

J'ai l'intuition que l'en-tête "Expires" de "0" est le coupable. Selon cet article du W3C , le mettre à "0" implique "déjà expiré". Je veux qu'il soit expiré, je ne veux simplement pas que IE l'enlève du système de fichiers avant que l'application qui le gère n'ait la possibilité de l'ouvrir.

Comme toujours, merci!

Edit: La solution

Lors de tests supplémentaires (en utilisant Fiddler pour inspecter les en-têtes), nous avons constaté que les en-têtes de réponse que nous pensions être en train de se définir n'étaient pas ceux qui étaient interprétés par le navigateur. N'ayant pas connu le code moi-même, je n'étais pas au courant d'un problème sous-jacent: les en-têtes étaient écrasés en dehors de la méthode d'action.

Néanmoins, je vais laisser cette question ouverte. Toujours en suspens, voici ceci: il semble exister une différence entre l’en-tête Expires ayant la valeur 0 et -1. Si quelqu'un peut prétendre à des différences par sa conception, en ce qui concerne IE, je voudrais tout de même en entendre parler. En ce qui concerne une solution, les en-têtes ci-dessus fonctionnent comme prévu avec la valeur Expires définie sur -1 dans tous les navigateurs.

Mise à jour 1

La publication Comment contrôler la mise en cache des pages Web sur tous les navigateurs? décrit en détail que la mise en cache peut être empêchée dans tous les navigateurs à l'aide de la définition de Expires = 0. Je ne suis toujours pas vendu sur cet argument 0 vs -1 ...

24
Cᴏʀʏ

Je pense que vous devriez juste utiliser

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

ou

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

pour définir max-age=0 qui ne signifie rien de plus car le cache est re-validé (voir ici ). Si vous définissez en plus ETag dans l'en-tête avec votre somme de contrôle personnalisée de hachage à partir des données, l'ETag de la requête précédente sera envoyé au serveur. Le serveur peut soit renvoyer les données, soit, si les données sont exactement les mêmes qu'auparavant, renvoyer un corps vide et HttpStatusCode.NotModified en tant que code d'état. Dans le cas où le navigateur Web obtiendra les données du cache du navigateur local.

Je vous recommande d'utiliser Cache-Control: private qui force deux choses importantes: 1) désactiver la mise en cache des données sur le proxy, qui possède des paramètres de mise en cache parfois très agressifs 2) cela permettra la mise en cache des données, mais pas le partage du cache avec un autre utilisateur. Cela peut résoudre des problèmes de confidentialité, car les données que vous renvoyez à un utilisateur ne sont peut-être pas autorisées à lire par un autre utilisateur. En passant, le code HttpContext.Current.Response.Cache.SetMaxAge (new TimeSpan (0)) définit par défaut Cache-Control: private, max-age=0 dans l'en-tête HTTP. Si vous souhaitez utiliser Cache-Control: public, vous pouvez utiliser SetCacheability (HttpCacheability.Public); pour écraser le comportement ou utiliser Headers.Set au lieu de Cache.SetMaxAge.

Si vous avez intérêt à étudier davantage d’options de mise en cache du protocole HTTP, je vous recommande de lire le didacticiel de mise en cache .

UPDATED: je décide d'écrire quelques informations supplémentaires pour effacer ma position. Correspond à les informations de Wikipedia, même si de vieux navigateurs Web tels que Mosaic 2.7, Netscape 2.0 et Internet Explorer 3.0 prennent en charge Mars 1996, le pré-standard de HTTP/1.1 décrit dans la RFC 2068. Donc, je suppose (mais pas le tester ) que les anciens navigateurs Web prennent en charge max-age=0 en-tête HTTP. Quoi qu'il en soit, Netscape 2.06 et Internet Explorer 4.0 prennent définitivement en charge HTTP 1.1.

Donc, vous devriez d’abord vous demander: quels standards HTML utilisez-vous? Utilisez-vous toujours HTML 2.0 au lieu de HTML 3.2 plus récent publié en janvier 1997? Je suppose que vous utilisez au moins HTML 4.0 publié en décembre 1997. Donc, si vous construisez votre application au moins en HTML 4.0, votre site peut être orienté sur les clients Web prenant en charge HTTP 1.1 et ignorer (ne prend pas en charge) les clients Web qui ne supporte pas HTTP 1.1.

Passons maintenant aux autres en-têtes "Cache-Control" comme "private, max-age = 0". Y compris des en-têtes est à mon avis est pure paranoïa . Comme j'ai moi-même un problème de cache, j'ai essayé d'inclure d'autres en-têtes, mais après avoir lu attentivement la section 14.9 de la RFC2616, j'utilise uniquement "Cache-Control: private, max-age = 0".

Le seul en-tête "Cache-Control" qui peut être discuté en plus est "must-revalidate" décrit dans la section 14.9.4 à laquelle j'ai fait référence précédemment. Voici la citation:

La directive must-revalidate est nécessaire pour prendre en charge un fonctionnement fiable Pour certaines fonctionnalités de protocole. Dans tous les cas, un cache HTTP/1.1 DOIT obéir à la directive must-revalidate; en particulier, si le cache ne peut pas atteindre le serveur Origin pour une raison quelconque, il DOIT générer une réponse 504 (Gateway Timeout).

Les serveurs DEVRAIENT envoyer la directive must-revalidate si et seulement si L'échec de la revalidation d'une demande sur l'entité pourrait entraîner une opération incorrecte, telle qu'une transaction financière Silencieusement non exécutée. Les destinataires NE DOIVENT PAS entreprendre d'action automatisée qui Viole cette directive, et NE DOIT PAS fournir automatiquement une copie Non validée de l'entité si la revalidation échoue.

Bien que cela ne soit pas recommandé, les agents utilisateurs opérant dans des conditions de connectivité strictes PEUVENT violer cette directive mais, dans ce cas, DOIVENT explicitement Avertir l'utilisateur qu'une réponse non validée a été fournie. L'avertissement DOIT être fourni pour chaque accès non validé et DEVRAIT Exiger une confirmation explicite de l'utilisateur.

Parfois, si j'ai un problème de connexion Internet, je vois la page vide avec le message "Gateway Timeout". Cela vient de l'utilisation de la directive "must-revalidate". Je ne pense pas que le message "Gateway Timeout" aide réellement l'utilisateur.

Ainsi, les personnes préférant commencer une procédure autodestructrice s’il entend le signal "Occupé" lors de l’appel de son patron, doit en outre utiliser la directive "must-revalidate" dans l’en-tête "Cache-Control". Les autres personnes que je recommande simplement d'utiliser "Cache-Control: private, max-age = 0" et rien de plus.

16
Oleg

Je sais que c'est tard, mais ce lien pourrait être une aide considérable pour toutes les personnes intéressées par le sujet: http://dotnet.dzone.com/articles/output-caching-aspnet-mvc

2
Rosdi Kasim

Pour IE, je me souviens avoir dû définir Expires: -1. Comment empêcher la mise en cache dans Internet Explorer semble confirmer cela avec l'extrait de code suivant.

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

En regardant en arrière dans le code, voici ce que j'ai trouvé. De plus, je me souviens vaguement que si vous définissez Cache-Control: private, il se peut qu’il ne se comporte pas correctement avec SSL.

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

Aussi, Alors, vous ne voulez pas mettre en cache, Hein? mentionne -1, mais utilise des méthodes sur Response.Cache à la place:

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

Cependant, ASP Problème de mise en cache de page (IE8) indique que ce code ne fonctionne pas.

0
Kevin Hakanson