C# 来自 HttpHandler 的图像不会在浏览器中缓存

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/994135/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 05:06:45  来源:igfitidea点击:

Image from HttpHandler won't cache in browser

c#.netasp.netihttphandler

提问by Jeff Putz

I'm serving up an image from a database using an IHttpHandler. The relevant code is here:

我正在使用 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;
}

The problem is that the browser won't cache the image, presumably because I'm not indicating the right thing in the response headers. The part calling methods on the HttpCachePolicy property is what I thought would force the browser to hold on to the image, but it doesn't. I think the "right" thing is for the handler to return a 304 status code without an image, right? How do I achieve that using IHttpHandler?

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

EDIT:

编辑:

Per the best answer, I got this code running and it completely solves the problem. Yes, it needs some refactoring, but it generally demonstrates what I was after. The relevant parts:

根据最佳答案,我运行了这段代码,它完全解决了问题。是的,它需要一些重构,但它通常展示了我所追求的。相关部分:

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

采纳答案by Thomas Jung

AFAIK, youare responsible for sending 304 Not Modified, meaning I am not aware of anything in the .Net framework that does it for you in this use case of you sending "dynamic" image data. What you will have to do (in pseudo code):

AFAIK,负责发送 304 Not Modified,这意味着在您发送“动态”图像数据的这个用例中,我不知道 .Net 框架中的任何内容为您做这件事。你必须做的(伪代码):

  • Check for the If-Modified-Since header in the request and parse out the date (if it exists).
  • Compare it to the last modification date of your original image (dynamically generated) image. Tracking this is probably the most complex part of the solution to this problem. In your current situation, you are re-creating the image on every request; you don'twant to do that unless you absolutely have to.
  • If the date of the file the browser has is newer or equal to what you have for the image, send a 304 Not Modified.
  • Otherwise, continue with your current implementation
  • 检查请求中的 If-Modified-Since 标头并解析出日期(如果存在)。
  • 将其与原始图像(动态生成的)图像的最后修改日期进行比较。跟踪这个可能是这个问题解决方案中最复杂的部分。在您目前的情况下,您正在根据每个请求重新创建图像;你希望这样做,除非你绝对必须的。
  • 如果浏览器拥有的文件日期更新或等于您拥有的图像日期,请发送 304 Not Modified。
  • 否则,继续您当前的实施

A simple way to track last modified times on your end is to cache newly generated images on the file system and keep an in-memory dictionary around that maps the image ID to a struct containing the file name on disk and the last modification date. Use Response.WriteFile to send the data from disk. Of course, every time you restart your worker process, the dictionary would be empty, but you're getting at least some caching benefit without having to deal with persisting caching information somewhere.

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

You can support this approach by separating the concerns of "Image Generation" and "Sending Images over HTTP" into different classes. Right now you're doing two very different things in the same place.

您可以通过将“图像生成”和“通过 HTTP 发送图像”的关注点分成不同的类来支持这种方法。现在你在同一个地方做两件截然不同的事情。

I know this may sound a little complex, but it's worth it. I just recently implemented this approach and the savings in processing time and bandwidth usage were incredible.

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

回答by David McEwing

Do you have any response buffering happening? If so you might want to set the headers before you write to the output stream. i.e. try moving the Response.OutputStream.Write()line down to below the Cache setting lines.

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

回答by Pavel Chuchuva

If you have source file on disk you can use this code:

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

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

Also, make sure that you test using IIS, not from Visual Studio. ASP.NET Development Server (aka Cassini) always sets Cache-Control to private.

此外,请确保您使用 IIS 进行测试,而不是使用 Visual Studio。ASP.NET 开发服务器(又名 Cassini)始终将 Cache-Control 设置为私有。

See also: Caching Tutorial for Web Authors and Webmasters

另请参阅:Web 作者和网站管理员的缓存教程

回答by Chris S

This is how it's done in Roadkill's(a .NET wiki) file handler:

这是在Roadkill 的(.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;

Thomas's answer about IIS not supplying the status code is the key, without it you just get 200s back each time.

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

The browser will simply send you a date and time for when it thinks the file was last modified (no no header at all), so if it differs you just return a 200. You do need to normalize your file's date to remove milliseconds and ensure it's a UTC date.

浏览器将简单地向您发送它认为文件上次修改的日期和时间(根本没有标题),因此如果它不同,您只需返回 200。您确实需要标准化文件的日期以删除毫秒并确保这是一个UTC日期。

I've gone for defaulting to 304s if there's a valid modified-since, but that can be tweaked if needed.

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