来自HttpHandler的图片不会在浏览器中缓存

时间:2009-06-15 00:41:53

标签: c# .net asp.net ihttphandler

我正在使用IHttpHandler从数据库提供图像。相关代码在这里:

public void ProcessRequest(HttpContext context)
{
    context.Response.ContentType = "image/jpeg";
    int imageID;
    if (int.TryParse(context.Request.QueryString["id"], out imageID))
    {
        var photo = new CoasterPhoto(imageID);
        if (photo.CoasterPhotoID == 0)
            context.Response.StatusCode = 404;
        else
        {
            byte[] imageData = GetImageData(photo);
            context.Response.OutputStream.Write(imageData, 0, imageData.Length);
            context.Response.Cache.SetCacheability(HttpCacheability.Public);
            context.Response.Cache.SetExpires(DateTime.Now.AddMinutes(5));
            context.Response.Cache.SetLastModified(photo.SubmitDate);
        }
    }
    else
        context.Response.StatusCode = 404;
}

问题是浏览器不会缓存图像,可能是因为我没有在响应头中指出正确的东西。我认为HttpCachePolicy属性上的部分调用方法会强制浏览器保留图像,但事实并非如此。我认为“正确”的事情是处理程序返回没有图像的304状态代码,对吧?如何使用IHttpHandler实现这一目标?

编辑:

根据最佳答案,我运行了此代码,它完全解决了问题。是的,它需要一些重构,但它通常会证明我追求的是什么。相关部分:

if (!String.IsNullOrEmpty(context.Request.Headers["If-Modified-Since"]))
{
    CultureInfo provider = CultureInfo.InvariantCulture;
    var lastMod = DateTime.ParseExact(context.Request.Headers["If-Modified-Since"], "r", provider).ToLocalTime();
    if (lastMod == photo.SubmitDate)
    {
        context.Response.StatusCode = 304;
        context.Response.StatusDescription = "Not Modified";
        return;
    }
}
byte[] imageData = GetImageData(photo);
context.Response.OutputStream.Write(imageData, 0, imageData.Length);
context.Response.Cache.SetCacheability(HttpCacheability.Public);
context.Response.Cache.SetLastModified(photo.SubmitDate);

4 个答案:

答案 0 :(得分:23)

AFAIK,负责发送304 Not Modified,这意味着在发送“动态”图像数据的用例中,我不知道.Net框架中有任何内容可以帮助您。您需要做什么(伪代码):

  • 检查请求中的If-Modified-Since标头并解析出日期(如果存在)。
  • 将其与原始图像(动态生成)图像的上次修改日期进行比较。跟踪这可能是解决此问题的最复杂部分。在您目前的情况下,您将在每个请求上重新创建图像;你想要这样做,除非你绝对必须这样做。
  • 如果浏览器的文件日期更新或等于图像的日期,请发送304 Not Modified。
  • 否则,继续执行当前的实施

跟踪最后修改时间的一种简单方法是在文件系统上缓存新生成的图像,并保留一个内存中的字典,将图像ID映射到包含磁盘上文件名的结构和最后修改日期。使用Response.WriteFile从磁盘发送数据。当然,每次重新启动工作进程时,字典都将为空,但您至少可以获得一些缓存优势,而无需在某处处理持久缓存信息。

您可以通过将“图像生成”和“通过HTTP发送图像”的问题分离到不同的类中来支持此方法。现在你在同一个地方做了两件截然不同的事情。

我知道这听起来有点复杂,但值得。我刚刚实施了这种方法,节省的处理时间和带宽使用量都令人难以置信。

答案 1 :(得分:7)

如果您在磁盘上有源文件,则可以使用以下代码:

context.Response.AddFileDependency(pathImageSource);
context.Response.Cache.SetETagFromFileDependencies();
context.Response.Cache.SetLastModifiedFromFileDependencies();
context.Response.Cache.SetCacheability(HttpCacheability.Public);

此外,请确保使用IIS进行测试,而不是使用Visual Studio进行测试。 ASP.NET Development Server(又名Cassini)始终将Cache-Control设置为private。

另请参阅:Caching Tutorial for Web Authors and Webmasters

答案 2 :(得分:6)

这是在Roadkill's(.NET wiki)文件处理程序中完成的:

FileInfo info = new FileInfo(fullPath);
TimeSpan expires = TimeSpan.FromDays(28);
context.Response.Cache.SetLastModifiedFromFileDependencies();
context.Response.Cache.SetETagFromFileDependencies();
context.Response.Cache.SetCacheability(HttpCacheability.Public);

int status = 200;
if (context.Request.Headers["If-Modified-Since"] != null)
{
    status = 304;
    DateTime modifiedSinceDate = DateTime.UtcNow;
    if (DateTime.TryParse(context.Request.Headers["If-Modified-Since"], out modifiedSinceDate))
    {
        modifiedSinceDate = modifiedSinceDate.ToUniversalTime();
        DateTime fileDate = info.LastWriteTimeUtc;
        DateTime lastWriteTime = new DateTime(fileDate.Year, fileDate.Month, fileDate.Day, fileDate.Hour, fileDate.Minute, fileDate.Second, 0, DateTimeKind.Utc);
        if (lastWriteTime != modifiedSinceDate)
            status = 200;
    }
}

context.Response.StatusCode = status;

托马斯关于IIS不提供状态代码的答案是关键,如果没有它,你每次只能获得200秒。

浏览器只会向您发送上次修改文件的日期和时间(根本没有标题),所以如果它不同,您只需返回200.您需要规范化文件的日期以删除毫秒并确保它是UTC日期。

如果有一个有效的修改后,我已经默认为304s,但如果需要可以调整。

答案 3 :(得分:0)

你有任何缓冲响应发生吗?如果是这样,您可能需要在写入输出流之前设置标头。即尝试将Response.OutputStream.Write()行向下移动到缓存设置行以下。