Evite el almacenamiento en caché en ASP.NET MVC para acciones específicas utilizando un atributo

196

Tengo una aplicación ASP.NET MVC 3. Esta aplicación solicita registros a través de jQuery. jQuery vuelve a llamar a una acción del controlador que devuelve resultados en formato JSON. No he podido probar esto, pero me preocupa que mis datos puedan estar almacenados en caché.

Solo quiero que el almacenamiento en caché se aplique a acciones específicas, no a todas las acciones.

¿Hay algún atributo que pueda poner en acción para garantizar que los datos no se almacenen en caché? Si no, ¿cómo me aseguro de que el navegador obtenga un nuevo conjunto de registros cada vez, en lugar de un conjunto en caché?

Desarrollador JavaScript
fuente
1
Si está adivinando que algo se está almacenando en caché, le recomiendo que lea sobre los mecanismos de control de caché aquí: w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9

Respuestas:

304

Para asegurarse de que JQuery no esté almacenando en caché los resultados, en sus métodos ajax, coloque lo siguiente:

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

O para evitar el almacenamiento en caché en MVC, creamos nuestro propio atributo, usted podría hacer lo mismo. Aquí está nuestro 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);
    }
}

Luego simplemente decora tu controlador con [NoCache]. O para hacerlo por todos, simplemente puede poner el atributo en la clase de la clase base de la que hereda sus controladores (si tiene uno) como tenemos aquí:

[NoCache]
public class ControllerBase : Controller, IControllerBase

También puede decorar algunas de las acciones con este atributo si necesita que no se puedan almacenar en caché, en lugar de decorar todo el controlador.

Si su clase o acción no tenía NoCachecuando se procesó en su navegador y desea verificar que funciona, recuerde que después de compilar los cambios, debe hacer una "actualización completa" (Ctrl + F5) en su navegador. Hasta que lo haga, su navegador mantendrá la versión en caché anterior y no la actualizará con una "actualización normal" (F5).

mattytommo
fuente
1
Intenté todo en la solución anterior y no me funciona.
Obi Wan
9
Tengo entendido (y no soy un experto en jQuery) que el caché: falso solo hace que jQuery agregue a la cadena de consulta un valor cambiante para "engañar" al navegador para que piense que la solicitud es para otra cosa. En teoría, esto significa que el navegador aún almacenaría en caché los resultados, simplemente no usaría los resultados almacenados en caché. Debería ser más eficiente en el cliente para deshabilitar el almacenamiento en caché a través de encabezados de respuesta.
Josh
2
Trabajó solo en el nivel del controlador y no en el nivel de acción.
Ramesh
3
Votaría a favor de
1
@ Frédéric, la sección de la especificación a la que apunta dice que los cachés no pueden almacenar en caché el contenido sin almacenamiento: La directiva de respuesta "sin almacenamiento" indica que un caché NO DEBE almacenar ninguna parte de la solicitud o respuesta inmediata.
kristianp
258

Puede usar el atributo de caché integrado para evitar el almacenamiento en caché.

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

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

Tenga en cuenta que es imposible forzar al navegador a deshabilitar el almacenamiento en caché. Lo mejor que puede hacer es proporcionar sugerencias que la mayoría de los navegadores cumplirán, generalmente en forma de encabezados o metaetiquetas. Este atributo decorador deshabilitar el caché del servidor y también añadir esta cabecera: Cache-Control: public, no-store, max-age=0. No agrega metaetiquetas. Si lo desea, se pueden agregar manualmente en la vista.

Además, JQuery y otros marcos de clientes intentarán engañar al navegador para que no use su versión en caché de un recurso agregando cosas a la url, como una marca de tiempo o GUID. Esto es efectivo para hacer que el navegador solicite nuevamente el recurso, pero en realidad no evita el almacenamiento en caché.

En una nota final. Debe tener en cuenta que los recursos también se pueden almacenar en caché entre el servidor y el cliente. Los ISP, los servidores proxy y otros dispositivos de red también almacenan recursos en caché y a menudo usan reglas internas sin mirar el recurso real. No hay mucho que puedas hacer al respecto. La buena noticia es que generalmente se almacenan en caché durante períodos de tiempo más cortos, como segundos o minutos.

Jaguir
fuente
3
Creo que esto no aborda completamente la cuestión. Esto deshabilita el almacenamiento en caché de ASP.NET pero no el almacenamiento en caché del navegador.
Rosdi Kasim
22
Es imposible forzar al navegador a deshabilitar el almacenamiento en caché. Lo mejor que puede hacer es proporcionar sugerencias que la mayoría de los navegadores cumplirán, generalmente en forma de encabezados o metaetiquetas. Este atributo decorador deshabilitará el almacenamiento en caché del servidor .NET y también agregará el encabezado Cache-Control:public, no-store, max-age=0. No agrega metaetiquetas. Si lo desea, se pueden agregar manualmente en la vista.
Jaguir
1
Puedo entender por qué usarías NoStore = truey Duration = 0(que he usado con éxito, gracias), pero ¿qué efecto adicional tendría VaryByParam = "None"ya que las otras dos opciones afectan todas las solicitudes independientemente del parámetro?
Se fue la codificación el
No creo que sea obligatorio en MVC, solo estaba siendo explícito. Recuerdo que en los formularios web ASP.NET y los controles de usuario, se requiere este atributo o el atributo VaryByControl.
Jaguir
1
Para el uso de ASP.NET Core: '[ResponseCache (NoStore = true, Duration = 0)]'
Jeff
48

Todo lo que necesitas es:

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

o, si desea deshabilitarlo para un controlador completo:

[OutputCache(Duration=0)]
public class MyController

A pesar del debate en los comentarios aquí, esto es suficiente para deshabilitar el almacenamiento en caché del navegador; esto hace que ASP.Net emita encabezados de respuesta que le indican al navegador que el documento caduca de inmediato:

OutputCache Duration = 0 Encabezados de respuesta: max-age = 0, s-maxage = 0

Chris Moschini
fuente
66
IE8 todavía representa la versión en caché de la página cuando se hace clic en el botón Atrás usando solo Duración = 0 en una Acción del Controlador. Usar NoStore = true junto con Duration = 0 (ver la respuesta de Jared) arregló el comportamiento en mi caso.
Keith Ketterer
3
Esto tiene el comportamiento un tanto curiosa de ajuste Cache-Controlapublic
ta.speot.is
max-age=0nunca ha significado 'caché deshabilitado'. Esto solo significa que el contenido de la respuesta debe considerarse inmediatamente obsoleto , pero se permite un caché para almacenarlo en caché. Los navegadores deben validar la actualización del contenido obsoleto en caché antes de usarlo, pero no es obligatorio a menos que must-revalidatese especifique la directiva adicional .
Frédéric
14

En la acción del controlador, agregue al encabezado las siguientes líneas

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

Aquí está el NoCacheatributo propuesto por mattytommo, simplificado usando la información de la respuesta de Chris Moschini:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class NoCacheAttribute : OutputCacheAttribute
{
    public NoCacheAttribute()
    {
        this.Duration = 0;
    }
}
Konamiman
fuente
Por alguna razón, MVC 3 no solo te permite establecer la duración en 0. Debes agregar estas anotaciones ... ¡gracias por la solución!
micahhoover
max-age=0nunca ha significado 'caché deshabilitado'. Esto solo significa que el contenido de la respuesta debe considerarse inmediatamente obsoleto , pero se permite un caché para almacenarlo en caché. Los navegadores deben validar la actualización del contenido obsoleto en caché antes de usarlo, pero no es obligatorio a menos que must-revalidatese especifique la directiva adicional .
Frédéric
Para completar, la directiva mínima y más apropiada es no-cache, que todavía permite el almacenamiento en caché, pero exige revalidar en el servidor de origen antes de cualquier uso. Para evitar incluso el almacenamiento en caché revalidado, debe agregarlo no-storejunto con no-cache. ( no-storesolo es claramente incorrecto porque los cachés volátiles pueden almacenar en caché el contenido marcado como no-store.)
Frédéric
4

Para MVC6 ( DNX ), no haySystem.Web.OutputCacheAttribute

Nota: cuando configura NoStore parámetro Duración no se considera. Es posible establecer una duración inicial para el primer registro y anular esto con atributos personalizados.

Pero tenemos Microsoft.AspNet.Mvc.Filters.ResponseCacheFilter

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

Es posible anular el filtro inicial con un 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);
        }
    }

Aquí hay un caso de uso

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

Caché de salida en MVC

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

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

Deepak
fuente
Vea otros comentarios ( 1 , 2 , 3 ) sobre las numerosas respuestas que ya sugieren usar esto. Su segunda línea es incorrecta y generará problemas con algunos navegadores.
Frédéric
1

El valor de atributo correcto para Asp.Net MVC Core para evitar el almacenamiento en caché del navegador (incluido Internet Explorer 11 ) es:

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

como se describe en la documentación de Microsoft:

Caché de respuestas en ASP.NET Core - NoStore y Location.None

Nenad
fuente
0

Soluciones ASP.NET MVC 5:

  1. Almacenamiento en caché de código de prevención en un lugar central: la 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. Una vez que tenga que, en lugar que mi entendimiento es que se puede anular el filtro global aplicando una diferente OutputCacheDirectiva en Controllero Viewnivel. Para el controlador regular es
[OutputCache(NoStore = true, Duration = 0, Location=System.Web.UI.ResponseCacheLocation.None, VaryByParam = "*")]

o si es un ApiControllersería

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