ASP.NET MVC:使浏览器从操作中缓存图像

时间:2012-01-31 23:29:12

标签: asp.net-mvc http caching

我有一个返回文件的actionmethod,只有一个参数(一个id)。

e.g。

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响应有一些显然应该工作的标题:

  

缓存控制:public,max-age = 360000

     

的Content-Length:39317

     

内容类型:image / PNG

     

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

     

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

     

Last-Modified:Tue,2012年1月31日23:20:56 GMT

但是请求标头有这个:

  

杂注:无缓存

有关如何做到这一点的任何想法?

非常感谢

5 个答案:

答案 0 :(得分:20)

首先要注意的是,当你在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操作方法)

答案 1 :(得分:13)

试试这段代码,它适用于我

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

答案 2 :(得分:1)

我使用过@ 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);
    }

    }
}

然后是Model类:

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 :(得分:0)

只是为了让你知道我发现了什么,并希望得到解释或更多信息,告诉我如何辨别。

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

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

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

拿出那些线条,你会发现你会再次开始击中你的断点。

我的问题:如果您仍然收到200响应,说明此图像是来自缓存的服务器的正确方法是什么。

这是用IIS Express 8测试的。用于Visual Studio的内置IIS不是GD ..

答案 4 :(得分:-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