2012-01-31 9 views
25

Tengo un método de acción que devuelve un archivo y tiene solo un argumento (una identificación).ASP.NET MVC: crear imágenes de caché del navegador de la acción

p. Ej.

public ActionResult Icon(long id) 
{ 
    return File(Server.MapPath("~/Content/Images/image" + id + ".png"), "image/png"); 
} 

que desea que el navegador almacena en caché de forma automática esta imagen la primera vez que acceder a ella por lo que la próxima vez que no tiene que descargar todos los datos.

He intentado utilizar cosas como OutputCacheAttribute y establecer manualmente los encabezados en la respuesta. es decir:

[OutputCache(Duration = 360000)] 

o

Response.Cache.SetCacheability(HttpCacheability.Public); 
Response.Cache.SetExpires(Cache.NoAbsoluteExpiration); 

Pero la imagen fija se carga cada vez que presione F5 en el navegador (Estoy intentando que en Chrome e IE). (Sé que está cargado todo el tiempo porque si cambio la imagen también cambia en el navegador).

veo que la respuesta HTTP tiene algunas cabeceras que aparentemente debería funcionar:

Cache-Control:, max-age = 360000 pública

Content-Length: 39317

Content- tipo: image/png

Fecha: Mar 31 Ene 2012 23:20:57 GMT

Expira: Dom 05 Feb 2012 03:20:56 GMT

-la última actualización: Mar 31 Ene 2012 23:20:56 GMT

Pero las cabeceras de petición tienen esta:

Pragma: no-cache

¿Alguna idea de cómo hacer esto?

Muchas gracias

Respuesta

19

Lo primero a destacar es que cuando se pulse F5 (actualizar) en Chrome, Safari o IE se solicitará de nuevo las imágenes, incluso si han sido almacenados en caché en el navegador.

Para decirle al navegador que no necesita volver a descargar la imagen, deberá devolver una respuesta 304 sin contenido, como se indica a continuación.

Response.StatusCode = 304; 
Response.StatusDescription = "Not Modified"; 
Response.AddHeader("Content-Length", "0"); 

Sin embargo, querrá comprobar el encabezado de la solicitud If-Modified-Since antes de devolver la respuesta 304. Por lo tanto, deberá verificar la fecha If-Modified-Since con la fecha modificada de su recurso de imagen (ya sea desde el archivo o almacenado en la base de datos, etc.). Si el archivo no ha cambiado, devuelva el 304; de lo contrario, devuelva con la imagen (recurso).

Éstos son algunos buenos ejemplos de la aplicación de esta funcionalidad (estos son para un HttpHandler pero los mismos principios se pueden aplicar al método de acción MVC) Trate

+2

Muchas gracias, justo lo que estaba buscando. Sin embargo, parece un poco complicado, ¿no debería haber una forma "más estándar" de hacerlo usando ASP.NET MVC? ¿Como un método que recibiría la respuesta y la última modificación y lo manejaría en consecuencia? (Eso es lo que haría, pero no creo que sea la primera persona que piense sobre esto). – willvv

+1

Siempre he usado mi propio HttpHandler, por lo que no es necesario usar MVC, pero eso no significa que se pueda hacer de esa manera, ¿no está seguro de qué manera funciona mejor? Puedo recomendar mirar http://imageresizing.net/, que es un gran pequeño componente que puede manejar todo para usted y más. – brodie

+0

El problema es que mis imágenes se almacenan en la base de datos y pueden modificarse externamente, por lo tanto, un controlador HTTP no parece una opción. – willvv

13

este código, funciona para mí

  HttpContext.Response.Cache.SetCacheability(HttpCacheability.Public); 
      HttpContext.Response.Cache.SetMaxAge(new TimeSpan(1, 0, 0)); 

      Entities.Image objImage = // get your Image form your database 

      string rawIfModifiedSince = HttpContext.Request.Headers.Get("If-Modified-Since"); 
      if (string.IsNullOrEmpty(rawIfModifiedSince)) 
      { 
       // Set Last Modified time 
       HttpContext.Response.Cache.SetLastModified(objImage.ModifiedDate); 
      } 
      else 
      { 
       DateTime ifModifiedSince = DateTime.Parse(rawIfModifiedSince); 


       // HTTP does not provide milliseconds, so remove it from the comparison 
       if (objImage.ModifiedDate.AddMilliseconds(
          -objImage.ModifiedDate.Millisecond) == ifModifiedSince) 
       { 
        // The requested file has not changed 
        HttpContext.Response.StatusCode = 304; 
        return Content(string.Empty); 
       } 
      } 

      return File(objImage.File, objImage.ContentType); 
0

Solo para informarle lo que he descubierto y me gustaría obtener una explicación o más información sobre cómo puedo saberlo.

Esto: Response.Cache.SetCacheability (HttpCacheability.Public); Response.Cache.SetExpires (Cache.NoAbsoluteExpiration);

de hecho caché de las imágenes .....

para probar esto ... prueba con depuración ... ponga punto de quiebre en el controlador de imagen ... y verá que no se golpeará una vez ... incluso si F5 un par de veces ... pero lo que es extraño para mí es que todavía se devuelve una respuesta de 200.

Tome esas líneas y verá que comenzará a golpear su punto de quiebre nuevamente.

Mi pregunta: ¿Cuál es la forma correcta de decir que esta imagen está siendo servidor desde la memoria caché si aún recibe una respuesta 200.

y esto estaba probando con IIS 8. Expresar la incorporada en el IIS para Visual Studio no está GD ..

1

He utilizado la solución de @ user2273400, funciona, así que estoy publicar solución completa.

Esta es mi controlador con acción y método ración temporal:

using System; 
using System.Web; 
using System.Web.Mvc; 
using CIMETY_WelcomePage.Models; 

namespace CIMETY_WelcomePage.Controllers 
{ 
    public class FileController : Controller 
    { 

     public ActionResult Photo(int userId) 
     { 
      HttpContext.Response.Cache.SetCacheability(HttpCacheability.Public); 
      HttpContext.Response.Cache.SetMaxAge(new TimeSpan(1, 0, 0)); 

      FileModel model = GetUserPhoto(userId); 


      string rawIfModifiedSince = HttpContext.Request.Headers.Get("If-Modified-Since"); 
      if (string.IsNullOrEmpty(rawIfModifiedSince)) 
      { 
       // Set Last Modified time 
       HttpContext.Response.Cache.SetLastModified(model.FileInfo.LastWriteTime); 
      } 
      else 
      { 
       DateTime ifModifiedSince = DateTime.Parse(rawIfModifiedSince); 


       // HTTP does not provide milliseconds, so remove it from the comparison 
       if (TrimMilliseconds(model.FileInfo.LastWriteTime.AddMilliseconds) <= ifModifiedSince) 
       { 
        // The requested file has not changed 
        HttpContext.Response.StatusCode = 304; 
        return Content(string.Empty); 
       } 
      } 

      return File(model.File, model.ContentType); 
     } 

     public FileModel GetUserPhoto(int userId) 
     { 
      string filepath = HttpContext.Current.Server.MapPath("~/Photos/635509890038594486.jpg"); 
      //string filepath = frontAdapter.GetUserPhotoPath(userId); 

      FileModel model = new FileModel(); 
      model.File = System.IO.File.ReadAllBytes(filepath); 
      model.FileInfo = new System.IO.FileInfo(filepath); 
      model.ContentType = MimeMapping.GetMimeMapping(filepath); 

      return model; 
     } 

    private DateTime TrimMilliseconds(DateTime dt) 
    { 
     return new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, dt.Second, 0); 
    } 

    } 
} 

Entonces la clase del modelo:

public class FileModel 
{ 
    public byte[] File { get; set; } 
    public FileInfo FileInfo { get; set; } 
    public String ContentType { get; set; } 
} 

Y cómo lo estoy usando:

<img src="@Url.Action("Photo", "File", new { userId = 15 })" /> 
-1

Editar: Leí mal la pregunta. Mi solución es para que el navegador no haga una nueva solicitud desde el servidor en la página abierta. Si el usuario presiona F5, el navegador solicitará los datos del servidor sin importar lo que haga con la información del caché. En ese caso, la solución es send an HTTP 304 as in @brodie's answer.


La solución más fácil que he encontrado para este problema fue utilizar OutputCacheAttribute.

Para su caso se debe utilizar más parámetros en el atributo OutputCache:

[OutputCache(Duration = int.MaxValue, VaryByParam = "id", Location=OutputCacheLocation.Client)] 
public ActionResult Icon(long id) 
{ 
    return File(Server.MapPath("~/Content/Images/image" + id + ".png"), "image/png"); 
} 

parámetro VaryByParam hace que el almacenamiento en caché de hecho basado en el id.De lo contrario, se enviará la primera imagen para todas las imágenes. Debe cambiar el parámetro Duración de acuerdo con sus requisitos. El parámetro Ubicación hace que el almacenamiento en caché solo en el navegador. Puede establecer la propiedad de Ubicación en cualquiera de los siguientes valores: Any, Client, Downstream, Server, None, ServerAndClient. De forma predeterminada, la propiedad Ubicación tiene el valor Cualquiera.

Para obtener información detallada lea:

http://www.asp.net/mvc/overview/older-versions-1/controllers-and-routing/improving-performance-with-output-caching-cs

Cuestiones relacionadas