Empêcher la mise en cache dans ASP.NET MVC pour des actions spécifiques à l'aide d'un attribut

196

J'ai une application ASP.NET MVC 3. Cette application demande des enregistrements via jQuery. jQuery rappelle une action de contrôleur qui renvoie des résultats au format JSON. Je n'ai pas pu le prouver, mais je crains que mes données ne soient mises en cache.

Je souhaite uniquement que la mise en cache soit appliquée à des actions spécifiques, pas à toutes les actions.

Existe-t-il un attribut que je peux mettre sur une action pour garantir que les données ne sont pas mises en cache? Sinon, comment puis-je m'assurer que le navigateur obtient un nouvel ensemble d'enregistrements à chaque fois, au lieu d'un ensemble mis en cache?

Développeur JavaScript
la source
1
Si vous devinez que quelque chose est mis en cache, je vous recommande de lire les mécanismes de contrôle du cache ici: w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9

Réponses:

304

Pour vous assurer que JQuery ne met pas les résultats en cache, sur vos méthodes ajax, mettez ce qui suit:

$.ajax({
    cache: false
    //rest of your ajax setup
});

Ou pour empêcher la mise en cache dans MVC, nous avons créé notre propre attribut, vous pouvez faire de même. Voici notre code:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class NoCacheAttribute : ActionFilterAttribute
{
    public override void OnResultExecuting(ResultExecutingContext filterContext)
    {
        filterContext.HttpContext.Response.Cache.SetExpires(DateTime.UtcNow.AddDays(-1));
        filterContext.HttpContext.Response.Cache.SetValidUntilExpires(false);
        filterContext.HttpContext.Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
        filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache);
        filterContext.HttpContext.Response.Cache.SetNoStore();

        base.OnResultExecuting(filterContext);
    }
}

Décorez ensuite votre contrôleur avec [NoCache]. OU pour le faire pour tout, vous pouvez simplement mettre l'attribut sur la classe de la classe de base dont vous héritez vos contrôleurs (si vous en avez un) comme nous l'avons ici:

[NoCache]
public class ControllerBase : Controller, IControllerBase

Vous pouvez également décorer certaines des actions avec cet attribut si vous avez besoin qu'elles ne soient pas mises en cache, au lieu de décorer l'ensemble du contrôleur.

Si votre classe ou action n'avait pas NoCachequand elle a été rendue dans votre navigateur et que vous voulez vérifier qu'elle fonctionne, rappelez-vous qu'après avoir compilé les modifications, vous devez effectuer une "actualisation matérielle" (Ctrl + F5) dans votre navigateur. Jusqu'à ce que vous le fassiez, votre navigateur conservera l'ancienne version mise en cache et ne la rafraîchira pas avec une "actualisation normale" (F5).

mattytommo
la source
1
J'ai tout essayé dans la solution ci-dessus et cela ne fonctionne pas pour moi.
Obi Wan
9
Je comprends (et je ne suis pas un expert de jQuery) que cache: false ne fait que jQuery coller à la chaîne de requête une valeur changeante pour "tromper" le navigateur en lui faisant croire que la demande concerne autre chose. En théorie, cela signifie que le navigateur mettrait toujours en cache les résultats, mais n'utiliserait tout simplement pas les résultats mis en cache. Devrait être plus efficace sur le client pour désactiver la mise en cache via les en-têtes de réponse.
Josh
2
Fonctionne uniquement au niveau du contrôleur et non au niveau de l'action.
Ramesh
3
Je voterais en faveur de l'inclusion d'un tel attribut dans le package officiel ASP.NET :-)
usr-local-ΕΨΗΕΛΩΝ
1
@ Frédéric, la section de la spécification que vous pointez indique que les caches ne peuvent pas mettre en cache le contenu sans stockage: la directive de réponse "sans stockage" indique qu'un cache NE DOIT PAS stocker de partie de la demande ou de la réponse immédiate.
kristianp
258

Vous pouvez utiliser l'attribut de cache intégré pour empêcher la mise en cache.

Pour .net Framework: [OutputCache(NoStore = true, Duration = 0)]

Pour .net Core: [ResponseCache(NoStore = true, Duration = 0)]

Sachez qu'il est impossible de forcer le navigateur à désactiver la mise en cache. Le mieux que vous puissiez faire est de fournir des suggestions que la plupart des navigateurs respecteront, généralement sous la forme d'en-têtes ou de balises META. Cet attribut décorateur désactive la mise en cache du serveur et ajouter cet en- tête: Cache-Control: public, no-store, max-age=0. Il n'ajoute pas de balises META. Si vous le souhaitez, ceux-ci peuvent être ajoutés manuellement dans la vue.

De plus, JQuery et d'autres frameworks clients tenteront d'inciter le navigateur à ne pas utiliser sa version mise en cache d'une ressource en ajoutant des éléments à l'URL, comme un horodatage ou un GUID. Ceci est efficace pour obliger le navigateur à demander à nouveau la ressource mais n'empêche pas vraiment la mise en cache.

Sur une note finale. Vous devez savoir que les ressources peuvent également être mises en cache entre le serveur et le client. Les FAI, les proxys et autres périphériques réseau mettent également en cache les ressources et utilisent souvent des règles internes sans regarder la ressource réelle. Il n'y a pas grand-chose que vous puissiez faire à ce sujet. La bonne nouvelle est qu'ils mettent généralement en cache pour des délais plus courts, comme les secondes ou les minutes.

Jaguir
la source
3
Je pense que cela ne résout pas complètement la question. Cela désactive la mise en cache ASP.NET mais pas la mise en cache du navigateur.
Rosdi Kasim
22
Il est impossible de forcer le navigateur à désactiver la mise en cache. Le mieux que vous puissiez faire est de fournir des suggestions que la plupart des navigateurs respecteront, généralement sous la forme d'en-têtes ou de balises META. Cet attribut décorateur désactivera la mise en cache du serveur .NET et ajoutera également l'en-tête Cache-Control:public, no-store, max-age=0. Il n'ajoute pas de balises META. Si vous le souhaitez, ceux-ci peuvent être ajoutés manuellement dans la vue.
Jaguir
1
Je peux comprendre pourquoi vous utiliseriez NoStore = trueet Duration = 0(que j'ai utilisé avec succès, merci), mais quel effet supplémentaire aurait VaryByParam = "None"comme les deux autres options affectent toutes les demandes quel que soit le paramètre?
Fin du codage le
Je ne pense pas que ce soit obligatoire dans MVC, j'étais juste explicite. Je me souviens que dans les formulaires Web ASP.NET et les contrôles utilisateur, cet attribut ou l'attribut VaryByControl est requis.
Jaguir
1
Pour ASP.NET Core, utilisez: '[ResponseCache (NoStore = true, Duration = 0)]'
Jeff
48

Tout ce dont tu as besoin c'est:

[OutputCache(Duration=0)]
public JsonResult MyAction(

ou, si vous souhaitez le désactiver pour un contrôleur entier:

[OutputCache(Duration=0)]
public class MyController

Malgré le débat dans les commentaires ici, cela suffit pour désactiver la mise en cache du navigateur - cela provoque ASP.Net à émettre des en-têtes de réponse qui indiquent au navigateur que le document expire immédiatement:

OutputCache Duration = 0 En-têtes de réponse: max-age = 0, s-maxage = 0

Chris Moschini
la source
6
IE8 affiche toujours la version mise en cache de la page lorsque vous cliquez sur le bouton de retour en utilisant uniquement Duration = 0 sur une action de contrôleur. L'utilisation de NoStore = true avec Duration = 0 (voir la réponse de Jared) a corrigé le comportement dans mon cas.
Keith Ketterer
3
Cela a le comportement quelque peu curieux de mettre Cache-Controlàpublic
ta.speot.is
max-age=0n'a jamais signifié «cache désactivé». Cela signifie seulement que le contenu de la réponse doit être pris en compte immédiatement périmé , mais un cache est autorisé à le mettre en cache. Les navigateurs doivent valider la fraîcheur du contenu périmé mis en cache avant de l'utiliser, mais ce n'est pas obligatoire sauf si la directive supplémentaire must-revalidateest spécifiée.
Frédéric
14

Dans l'action du contrôleur, ajoutez à l'en-tête les lignes suivantes

    public ActionResult Create(string PositionID)
    {
        Response.AppendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1.
        Response.AppendHeader("Pragma", "no-cache"); // HTTP 1.0.
        Response.AppendHeader("Expires", "0"); // Proxies.
dfortun
la source
5

Voici l' NoCacheattribut proposé par mattytommo, simplifié en utilisant les informations de la réponse de Chris Moschini:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class NoCacheAttribute : OutputCacheAttribute
{
    public NoCacheAttribute()
    {
        this.Duration = 0;
    }
}
Konamiman
la source
Pour une raison quelconque, MVC 3 ne vous permet pas seulement de définir la durée à 0. Vous devez ajouter ces annotations ... merci pour la solution!
micahhoover
max-age=0n'a jamais signifié «cache désactivé». Cela ne signifie que le contenu de la réponse doit être considéré comme immédiatement périmé , mais un cache est autorisé à le mettre en cache. Les navigateurs doivent valider la fraîcheur du contenu périmé mis en cache avant de l'utiliser, mais ce n'est pas obligatoire sauf si la directive supplémentaire must-revalidateest spécifiée.
Frédéric
Pour être complet, la directive minimale et plus appropriée est no-cache, qui permet toujours la mise en cache mais mandat de revalider le serveur d'origine avant toute utilisation. Pour éviter même la mise en cache revalidée, vous devez ajouter no-storeavec no-cache. ( no-storeseul est clairement faux parce que les caches volatiles sont autorisés à mettre en cache le contenu marqué comme no-store.)
Frédéric
4

Pour MVC6 ( DNX ), il n'y a pasSystem.Web.OutputCacheAttribute

Remarque: lorsque vous définissez NoStore paramètre Durée n'est pas pris en compte. Il est possible de définir une durée initiale pour le premier enregistrement et de la remplacer par des attributs personnalisés.

Mais nous avons Microsoft.AspNet.Mvc.Filters.ResponseCacheFilter

 public void ConfigureServices(IServiceCollection services)
        ...
        services.AddMvc(config=>
        {
            config.Filters.Add(
                 new ResponseCacheFilter(
                    new CacheProfile() { 
                      NoStore=true
                     }));
        }
        ...
       )

Il est possible de remplacer le filtre initial avec un attribut personnalisé

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
    public sealed class NoCacheAttribute : ActionFilterAttribute
    {
        public override void OnResultExecuting(ResultExecutingContext filterContext)
        {
            var filter=filterContext.Filters.Where(t => t.GetType() == typeof(ResponseCacheFilter)).FirstOrDefault();
            if (filter != null)
            {
                ResponseCacheFilter f = (ResponseCacheFilter)filter;
                f.NoStore = true;
                //f.Duration = 0;
            }

            base.OnResultExecuting(filterContext);
        }
    }

Voici un cas d'utilisation

    [NoCache]
    [HttpGet]
    public JsonResult Get()
    {            
        return Json(new DateTime());
    }
Davut Gürbüz
la source
1

Mise en cache de sortie dans MVC

[OutputCache (NoStore = true, Duration = 0, Location = "None", VaryByParam = "*")]

OU
[OutputCache (NoStore = true, Duration = 0, VaryByParam = "None")]

Deepak
la source
Voir d'autres commentaires ( 1 , 2 , 3 ) sur les nombreuses réponses suggérant déjà d'utiliser ceci. Votre deuxième ligne est incorrecte et entraînera des problèmes avec certains navigateurs.
Frédéric
0

Solutions ASP.NET MVC 5:

  1. Mise en cache du code de prévention à un emplacement central: la App_Start/FilterConfig.csde » RegisterGlobalFiltersméthode:
    public class FilterConfig
    {
        public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            // ...
            filters.Add(new OutputCacheAttribute
            {
                NoStore = true,
                Duration = 0,
                VaryByParam = "*",
                Location = System.Web.UI.OutputCacheLocation.None
            });
        }
    }
  1. Une fois que vous avez cela en place, je comprends que vous pouvez remplacer le filtre global en appliquant une OutputCachedirective différente au niveau Controllerou au Viewniveau. Pour le contrôleur ordinaire, c'est
[OutputCache(NoStore = true, Duration = 0, Location=System.Web.UI.ResponseCacheLocation.None, VaryByParam = "*")]

ou si c'est un ApiControllerce serait

[System.Web.Mvc.OutputCache(NoStore = true, Duration = 0, Location = System.Web.UI.OutputCacheLocation.None, VaryByParam = "*")]
Csaba Toth
la source