【问题标题】:ASP.NET MVC: Make browser cache images from actionASP.NET MVC:通过动作使浏览器缓存图像
【发布时间】:2012-02-23 17:00:57
【问题描述】:

我有一个动作方法,它返回一个文件并且只有一个参数(一个 id)。

例如

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

我希望浏览器在我第一次访问它时自动缓存此图像,以便下次它不必下载所有数据。

我尝试过使用 OutputCacheAttribute 之类的东西,并在响应中手动设置标头。即:

[OutputCache(Duration = 360000)]

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

但是每次我在浏览器上按 F5 时,图像仍然会加载(我正在 Chrome 和 IE 上尝试)。 (我知道它每次都会加载,因为如果我更改图像,它也会在浏览器中更改)。

我看到 HTTP 响应有一些显然应该工作的标头:

Cache-Control:public, max-age=360000

内容长度:39317

内容类型:图片/png

日期:2012 年 1 月 31 日星期二 23:20:57 GMT

到期时间:2012 年 2 月 5 日星期日 03:20:56 GMT

最后修改时间:格林威治标准时间 2012 年 1 月 31 日星期二 23:20:56

但是请求头有这个:

Pragma:no-cache

知道如何做到这一点吗?

非常感谢

【问题讨论】:

    标签: asp.net-mvc http caching


    【解决方案1】:

    编辑:我误读了这个问题。我的解决方案是,浏览器不会在页面打开时从服务器发出新请求。如果用户按下 F5,无论你对缓存信息做什么,浏览器都会向服务器请求数据。在这种情况下,解决方案是send an HTTP 304 as in @brodie's answer


    我发现这个问题最简单的解决方案是使用 OutputCacheAttribute。

    对于您的情况,您必须在 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");
    }
    

    VaryByParam 参数使缓存基于 id 完成。否则,将为所有图像发送第一张图像。 您应该根据您的要求更改 Duration 参数。 Location 参数仅在浏览器上进行缓存。您可以将 Location 属性设置为以下任一值:Any、Client、Downstream、Server、None、ServerAndClient。默认情况下,Location 属性的值为 Any。

    详细信息请阅读:

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

    【讨论】:

      【解决方案2】:

      我使用了@user2273400 的解决方案,它有效,所以我发布了完整的解决方案。

      这是我的控制器,带有操作和临时帮助方法:

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

      然后是模型类:

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

      以及我是如何使用它的:

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

      【讨论】:

        【解决方案3】:

        只是为了让您知道我发现了什么,并希望获得有关我如何判断的解释或更多信息。

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

        实际上缓存图像.....

        为了证明这一点...用调试进行测试...在图像控制器上设置断点... 你会看到它不会被击中一次......即使你F5几次...... 但对我来说奇怪的是仍然返回 200 响应。

        去掉这些行,你会发现你会再次开始到达你的断点。

        我的问题:如果您仍然收到 200 响应,那么判断此图像是否正在从缓存中服务器的正确方法是什么。

        这是使用 IIS express 8 进行的测试。Visual Studio 的内置 IIS 不是 GD..

        【讨论】:

          【解决方案4】:

          试试这个代码,它对我有用

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

          【讨论】:

            【解决方案5】:

            首先要注意的是,当您在 Chrome、Safari 或 IE 中按 F5(刷新)时,将再次请求图像,即使它们已被缓存在浏览器中。

            要告诉浏览器它不需要再次下载图像,您需要返回一个没有内容的 304 响应,如下所示。

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

            您需要在返回 304 响应之前检查 If-Modified-Since 请求标头。因此,您需要根据图像资源的修改日期检查 If-Modified-Since 日期(无论是来自文件还是存储在数据库中等)。如果文件没有改变则返回 304,否则返回图片(资源)。

            这里有一些很好的例子来实现这个功能(这些是针对 HttpHandler 但同样的原则也可以应用于 MVC 动作方法)

            【讨论】:

            • 非常感谢,正是我想要的。不过,它看起来确实有点复杂,难道不应该有一种“更标准”的方式来使用 ASP.NET MVC 吗?就像一个可以接收响应和最新修改并相应地处理它的方法? (这就是我会做的,但我不认为我是第一个考虑这个问题的人)。
            • 我一直使用自己的 HttpHandler - 所以不需要使用 MVC,但并不意味着可以这样做 - 只是不确定哪种方式性能更好?我可以推荐查看imageresizing.net,这是一个很棒的小组件,可以为您处理所有事情等等!
            • 问题是我的图像存储在数据库中并且可以在外部进行修改,因此 HTTP 处理程序似乎不是一个选项。
            • 一个 HttpHandler 可以像任何其他可执行代码一样访问数据库或文件系统——只要权限正确等等。我使用的 HttpHandler 可以:) 所以你的资源处理代码不会不需要特定于 MVC - 请记住 MVC 构建在 ASP.NET 之上
            • 是的,我知道,但这意味着将特定于应用程序的代码放入我不喜欢的 http 处理程序(我喜欢我的处理程序是通用的)。
            猜你喜欢
            • 2014-03-30
            • 2014-06-30
            • 1970-01-01
            • 2014-04-23
            • 2015-12-07
            • 1970-01-01
            • 2011-03-02
            • 1970-01-01
            • 2011-11-12
            相关资源
            最近更新 更多