Impedir o armazenamento em cache no ASP.NET MVC para ações específicas usando um atributo

196

Eu tenho um aplicativo ASP.NET MVC 3. Este aplicativo solicita registros através do jQuery. O jQuery chama de volta para uma ação do controlador que retorna resultados no formato JSON. Não pude provar isso, mas estou preocupado que meus dados estejam ficando em cache.

Quero apenas que o cache seja aplicado a ações específicas, não a todas as ações.

Existe um atributo que eu possa colocar em uma ação para garantir que os dados não sejam armazenados em cache? Caso contrário, como posso garantir que o navegador obtenha um novo conjunto de registros a cada vez, em vez de um conjunto em cache?

Desenvolvedor JavaScript
fonte
1
Se você está achando que algo está sendo armazenada em cache, então eu recomendo que você leia-se sobre os mecanismos de controle de cache aqui: w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9

Respostas:

304

Para garantir que o JQuery não esteja armazenando em cache os resultados, em seus métodos ajax, coloque o seguinte:

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

Ou para impedir o armazenamento em cache no MVC, criamos nosso próprio atributo, você pode fazer o mesmo. Aqui está o nosso código:

[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);
    }
}

Em seguida, basta decorar seu controlador com [NoCache]. OU para fazer isso por tudo, você pode simplesmente colocar o atributo na classe da classe base da qual você herda seus controladores (se você tiver um) como nós temos aqui:

[NoCache]
public class ControllerBase : Controller, IControllerBase

Você também pode decorar algumas das ações com esse atributo se precisar que elas não sejam armazenáveis ​​em cache, em vez de decorar todo o controlador.

Se a sua classe ou ação não teve NoCachequando foi renderizada no seu navegador e você deseja verificar se está funcionando, lembre-se de que, após compilar as alterações, é necessário fazer uma "atualização definitiva" (Ctrl + F5) no seu navegador. Até você fazer isso, seu navegador manterá a versão antiga em cache e não a atualizará com uma "atualização normal" (F5).

mattytommo
fonte
1
Eu tentei de tudo na solução acima e não funciona para mim.
Obi Wan
9
É meu entendimento (e eu não sou especialista em jQuery) que o cache: false apenas torna o tack do jQuery na string de consulta um valor variável para "enganar" o navegador a pensar que a solicitação é para outra coisa. Em teoria, isso significa que o navegador ainda armazenaria em cache os resultados, apenas não os usaria em cache. Deve ser mais eficiente no cliente desativar o cache por meio de cabeçalhos de resposta.
27413 Josh
2
Funcionou apenas no nível do controlador e não no nível de ação.
Ramesh
3
Gostaria de votar, incluindo esse atributo no pacote oficial do ASP.NET :-)
usr-local-ΕΨΗΕΛΩΝ
1
@ Frédéric, a seção da especificação que você aponta diz que os caches não podem armazenar em cache o conteúdo sem armazenamento: A diretiva de resposta "sem armazenamento" indica que um cache NÃO DEVE armazenar nenhuma parte da solicitação ou resposta imediata.
22416 kristianp #
258

Você pode usar o atributo de cache interno para impedir o armazenamento em cache.

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

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

Esteja ciente de que é impossível forçar o navegador a desativar o cache. O melhor que você pode fazer é fornecer sugestões que a maioria dos navegadores respeitará, geralmente na forma de cabeçalhos ou metatags. Este atributo decorador irá desativar o cache do servidor e também adicionar este cabeçalho: Cache-Control: public, no-store, max-age=0. Não adiciona meta tags. Se desejado, esses podem ser adicionados manualmente na exibição.

Além disso, o JQuery e outras estruturas de clientes tentarão induzir o navegador a não usar sua versão em cache de um recurso adicionando itens ao URL, como um carimbo de data / hora ou GUID. Isso é eficaz para fazer o navegador solicitar o recurso novamente, mas na verdade não impede o armazenamento em cache.

Em uma nota final. Você deve estar ciente de que os recursos também podem ser armazenados em cache entre o servidor e o cliente. Os ISP, proxies e outros dispositivos de rede também armazenam em cache recursos e geralmente usam regras internas sem examinar o recurso real. Não há muito que você possa fazer sobre isso. A boa notícia é que eles geralmente armazenam em cache por períodos mais curtos, como segundos ou minutos.

Jaguir
fonte
3
Eu acredito que isso não trata completamente da questão. Isso desativa o cache do ASP.NET, mas não o cache do navegador.
Rosdi Kasim
22
É impossível forçar o navegador a desativar o cache. O melhor que você pode fazer é fornecer sugestões que a maioria dos navegadores respeitará, geralmente na forma de cabeçalhos ou metatags. Esse atributo do decorador desabilitará o cache do servidor .NET e também adicionará o cabeçalho Cache-Control:public, no-store, max-age=0. Não adiciona meta tags. Se desejado, esses podem ser adicionados manualmente na exibição.
Jaguir
1
Eu posso entender por que você usaria NoStore = truee Duration = 0(que eu usei com sucesso, obrigado), mas que efeito adicional teria VaryByParam = "None"como as outras duas opções afetam todas as solicitações, independentemente do parâmetro?
Gone Coding
Eu não acho que isso seja necessário no MVC, eu estava apenas sendo explícito. Lembro-me de que, nos formulários da Web do ASP.NET e nos controles do usuário, esse atributo ou o atributo VaryByControl são necessários.
Jaguir
1
Para o ASP.NET Core, use: '[ResponseCache (NoStore = true, Duração = 0)]'
Jeff
48

Tudo o que você precisa é:

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

ou, se você deseja desativá-lo para um controlador inteiro:

[OutputCache(Duration=0)]
public class MyController

Apesar do debate nos comentários aqui, isso é suficiente para desativar o cache do navegador - isso faz com que o ASP.Net emita cabeçalhos de resposta que informam ao navegador que o documento expira imediatamente:

OutputCache Duration = 0 Response Headers: max-age = 0, s-maxage = 0

Chris Moschini
fonte
6
O IE8 ainda renderiza a versão em cache da página quando o botão Voltar é clicado usando apenas Duração = 0 em uma Ação do Controlador. Usar NoStore = true junto com Duration = 0 (consulte a resposta de Jared) corrigiu o comportamento no meu caso.
precisa
3
Isto tem o comportamento um tanto curiosa de configuração Cache-Controlparapublic
ta.speot.is
max-age=0nunca significou 'cache desativado'. Isso significa apenas que o conteúdo da resposta deve ser considerado imediatamente obsoleto , mas é permitido que um cache o armazene em cache. Os navegadores devem validar a atualização do conteúdo antigo armazenado em cache antes de usá-lo, mas não é obrigatório, a menos que a diretiva adicional must-revalidateseja especificada.
Frédéric
14

Na ação do controlador, adicione ao cabeçalho as seguintes linhas

    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
fonte
5

Aqui está o NoCacheatributo proposto por mattytommo, simplificado usando as informações da resposta de Chris Moschini:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class NoCacheAttribute : OutputCacheAttribute
{
    public NoCacheAttribute()
    {
        this.Duration = 0;
    }
}
Konamiman
fonte
Por alguma razão, o MVC 3 não permite apenas definir a duração como 0. Você precisa adicionar essas anotações ... obrigado pela solução alternativa!
Micahhoover 19/03/2015
max-age=0nunca significou 'cache desativado'. Isso significa apenas que o conteúdo da resposta deve ser considerado imediatamente obsoleto , mas é permitido que um cache o armazene em cache. Os navegadores devem validar a atualização do conteúdo antigo armazenado em cache antes de usá-lo, mas não é obrigatório, a menos que a diretiva adicional must-revalidateseja especificada.
Frédéric
Para completar, é a diretiva mínima e mais apropriada no-cache, que ainda permite o armazenamento em cache, mas exige a revalidação no servidor de origem antes de qualquer uso. Para evitar o armazenamento em cache revalidado, é necessário adicionar no-storejunto no-cache. ( no-storeSozinho é errado simplesmente porque caches voláteis estão autorizados a cache de conteúdo marcado como no-store.)
Frédéric
4

Para MVC6 ( DNX ), não háSystem.Web.OutputCacheAttribute

Nota: quando você define o NoStoreparâmetro Duration, não é considerado. É possível definir uma duração inicial para o primeiro registro e substituí-la por atributos personalizados.

Mas nos temos Microsoft.AspNet.Mvc.Filters.ResponseCacheFilter

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

É possível substituir o filtro inicial por um atributo personalizado

    [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);
        }
    }

Aqui está um caso de uso

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

Cache de saída no MVC

[OutputCache (NoStore = true, Duração = 0, Localização = "Nenhuma", VaryByParam = "*")]

OU
[OutputCache (NoStore = true, Duração = 0, VaryByParam = "None")]

Deepak
fonte
Veja outros comentários ( 1 , 2 , 3 ) sobre as inúmeras respostas que já sugerem o uso disso. Sua segunda linha está errada e causará problemas em alguns navegadores.
Frédéric
1

O valor correto do atributo para o Asp.Net MVC Core para impedir o cache do navegador (incluindo o Internet Explorer 11 ) é:

[ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)]

conforme descrito na documentação da Microsoft:

Cache de resposta no ASP.NET Core - NoStore e Location.None

Nenad
fonte
0

Soluções ASP.NET MVC 5:

  1. Caching código de prevenção em um local central: o App_Start/FilterConfig.cs's RegisterGlobalFiltersmétodo:
    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. Depois de implementá-lo, entendo que você pode substituir o filtro global aplicando uma OutputCachediretiva Controllerou Viewnível diferente. Para o controlador regular, é
[OutputCache(NoStore = true, Duration = 0, Location=System.Web.UI.ResponseCacheLocation.None, VaryByParam = "*")]

ou se é ApiControllerque seria

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