2012-01-31 14 views
25

Ho un metodo di azione che restituisce un file e ha un solo argomento (un id).ASP.NET MVC: rende le immagini della cache del browser dall'azione

ad es.

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

Voglio il browser di memorizzare nella cache automaticamente questa immagine la prima volta che accedo così la prossima volta che non ha bisogno di scaricare tutti i dati.

Ho provato a utilizzare cose come OutputCacheAttribute e ad impostare manualmente le intestazioni sulla risposta. cioè:

[OutputCache(Duration = 360000)] 

o

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

Ma l'immagine è ancora caricato ogni volta che mi ha colpito F5 sul browser (che sto cercando su Chrome e IE). (So ​​che viene caricato ogni volta perché se cambio l'immagine cambia anche nel browser).

vedo che la risposta HTTP ha alcune intestazioni che a quanto pare dovrebbe funzionare:

Cache-Control: max-age pubblico = 360000

Content-Length: 39317

Content- tipo: image/png

Data: martedì, 31 gennaio 2012 23:20:57 GMT

Scadenza: Sun, 5 febbraio 2012 03:20:56 GMT

Last-Modified: Mar 31 Gen 2012 23:20:56 GMT

Ma le intestazioni di richiesta hanno questo:

Pragma: no-cache

Qualche idea su come fare questo?

Grazie mille

risposta

19

La prima cosa da notare è che quando si colpisce F5 (refresh) in Chrome, Safari o IE le immagini verranno richiesti di nuovo, anche se sono stati memorizzati nella cache del browser.

Per comunicare al browser che non è necessario scaricare nuovamente l'immagine, è necessario restituire una risposta 304 senza contenuto, come di seguito.

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

Si vorrà controllare l'intestazione della richiesta If-Modified-Since prima di restituire la risposta 304. Quindi dovrai controllare la data If-Modified-Since rispetto alla data modificata della tua risorsa immagine (che sia dal file o archiviata nel database, ecc.). Se il file non è cambiato, restituire il 304, altrimenti tornare con l'immagine (risorsa).

Ecco alcuni buoni esempi di implementazione di questa funzionalità (questi sono per un HttpHandler ma gli stessi principi possono essere applicati al metodo di azione MVC)

+2

Grazie mille, proprio quello che stavo cercando. Sembra un po 'complicato, però, non dovrebbe esserci un modo "più standard" di farlo usando ASP.NET MVC? Come un metodo che riceverebbe la risposta e l'ultima modifica e gestirla di conseguenza? (Questo è quello che farei, ma non penso di essere il primo a pensarci). – willvv

+1

Ho sempre usato il mio HttpHandler - quindi non c'è bisogno di usare MVC, ma non significa che possa essere fatto in questo modo - semplicemente non è sicuro quale sia il modo migliore di comportarsi? Posso consigliare di guardare http://imageresizing.net/ che è un piccolo grande componente in grado di gestire tutto per te e altro ancora! – brodie

+0

Il problema è che le mie immagini sono memorizzate nel database e possono essere modificate esternamente, quindi un gestore HTTP non sembra un'opzione. – willvv

13

Try questo codice, funziona per me

  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 per farti sapere cosa ho scoperto e vorrei una spiegazione o ulteriori informazioni su come posso dire.

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

in effetti la cache delle immagini .....

per dimostrare questo ... prova con debug ... mettere punto di interruzione sul controller immagine ... e vedrete che non verrà colpito una volta ... anche se F5 un paio di volte ... ma ciò che è strano per me è che viene restituita una risposta di 200.

Prendi queste righe e vedrai che inizierai a colpire di nuovo il punto di interruzione.

La mia domanda: Qual è il modo corretto per dire che questa immagine è server dalla cache se si riceve ancora una risposta 200.

e questo stava mettendo alla prova con IIS esprimono 8. Il built-in IIS per Visual Studio non è GD ..

1

Ho usato la soluzione da @ user2273400, funziona, così mi distacco soluzione completa.

Questo è il mio controller con l'azione e il metodo porzione temporanea:

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

    } 
} 

Poi la classe del modello:

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

e come io sto usando:

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

Edit: ho letto male la domanda. La mia soluzione è in modo che il browser non faccia una nuova richiesta dal server a pagina aperta. Se l'utente preme F5, il browser richiederà i dati dal server, indipendentemente da ciò che si fa sulle informazioni della cache. In tal caso, la soluzione è send an HTTP 304 as in @brodie's answer.


La soluzione più semplice che ho trovato a questo problema era utilizzare OutputCacheAttribute.

Per il vostro caso è necessario utilizzare più parametri nell'attributo 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"); 
} 

parametro VaryByParam rende il caching fatto in base all'ID.Altrimenti, la prima immagine verrà inviata per tutte le immagini. È necessario modificare il parametro Durata in base ai propri requisiti. Il parametro Posizione rende la memorizzazione nella cache solo sul browser. È possibile impostare la proprietà Location su uno dei seguenti valori: Qualsiasi, Client, Downstream, Server, None, ServerAndClient. Per impostazione predefinita, la proprietà Location ha il valore Any.

Per informazioni dettagliate leggere:

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

Problemi correlati