【问题标题】:Client side caching using Last-Modified header and OutputCacheAttribute in asp.net mvc 3在 asp.net mvc 3 中使用 Last-Modified 标头和 OutputCacheAttribute 进行客户端缓存
【发布时间】:2013-02-01 13:56:11
【问题描述】:

已编辑

我想在客户端缓存图片,并且知道在mvc 3中有不同的方法可以做到:(如果我错了,请纠正我)

1) 您可以使用OutputCacheAttribute,它在Expires http 标头的帮助下工作。但它会返回304 Not Modified,除非时间到期(即使图像已更改)。

2) 为避免显示陈旧图像,您可以使用Last-Modified http 标头(带有OutputCacheAttribute)。在这种情况下,浏览器将带有If-Modified-Since http 标头的请求发送到服务器。在服务器上,您验证对象是否仍然有效,如果是,您只需返回 Last-Modified http 标头(浏览器从本地缓存中获取图像);如果对象已被修改,则返回 200 OK 状态。
因此,浏览器每次从自己的缓存中获取图像之前都需要向服务器发送请求。 Here is the example -

3) 还有另一种方法(在我的情况下,我被告知正确的方法,因为图像很少会改变......无论如何,我需要完全实现这一点):将修改日期添加到图像 url 和使用Expires 为永恒(1 年或更长时间)设置缓存。如果图像已更改,您应该发送带有新版本的新网址。

代码如下:

public class LastModifiedCacheAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        if (filterContext.Result is FilePathResult)
        {
            var result = (FilePathResult)filterContext.Result;
            var lastModify = File.GetLastWriteTime(result.FileName);
            if (!HasModification(filterContext.RequestContext, lastModify))
                filterContext.Result = NotModified(filterContext.RequestContext, lastModify);
            SetLastModifiedDate(filterContext.RequestContext, lastModify);
        }
        base.OnActionExecuted(filterContext);
    }

    private static void SetLastModifiedDate(RequestContext requestContext, DateTime modificationDate)
    {
        requestContext.HttpContext.Response.Cache.SetLastModified(modificationDate);
    }

    private static bool HasModification(RequestContext context, DateTime modificationDate)
    {
        var headerValue = context.HttpContext.Request.Headers["If-Modified-Since"];
        if (headerValue == null)
            return true;
        var modifiedSince = DateTime.Parse(headerValue).ToLocalTime();
        return modifiedSince < modificationDate;
    }

    private static ActionResult NotModified(RequestContext response, DateTime lastModificationDate)
    {
        response.HttpContext.Response.Cache.SetLastModified(lastModificationDate);
        return new HttpStatusCodeResult(304, "Page has not been modified");
    }
}

我在 Global.asax 中注册了LastModifiedCacheAttribute,并将以下 OutputCacheAttribute 应用于我的操作方法。

[HttpGet, OutputCache(Duration = 3600, Location = OutputCacheLocation.Client, VaryByParam = "productId")]
public FilePathResult GetImage(int productId)
{ // some code }

如果我使用上面的代码,浏览器似乎不会向服务器发送请求,而是只会从缓存中获取图像,除非持续时间没有结束。 (当我更改图像时,浏览器不显示新版本)

问题:

1) 如何实现第三种方法,使浏览器从客户端缓存中获取图像(并且不会在每次需要图像时向服务器发送响应),除非图像被修改了吗?
已编辑:实际代码将不胜感激。

2) 在上面的代码中,第一个图像请求的时间被写入Last-Modified(不知道为什么)。如何将文件的修改日期写入 Last-Modified?
edited: 这个问题与第二种方法有关。此外,如果我仅在客户端缓存并使用Last-Modified 实现,则只有在按下F5 时才会获得304 Not Modified 状态。如果我重新输入相同的网址,我将得到200 OK。如果我在不使用Last-Modified 的情况下缓存在客户端上,无论如何它都会返回200 OK。这怎么解释?

【问题讨论】:

    标签: asp.net asp.net-mvc asp.net-mvc-3 caching browser-cache


    【解决方案1】:

    您可以考虑使用 ETags (http://en.wikipedia.org/wiki/HTTP_ETag),这是我阅读您的问题时想到的第一件事。

    你也可以看看这里:Set ETag for FileResult - MVC 3

    【讨论】:

    • 感谢您的链接。我认为 ETag 的工作方式与 Last-Modified 相同。唯一的区别是 Last-Modified 是一个日期,而 Etag 是一个标识符。我已经实现了 Windows 服务应用程序,它监视图像文件夹中的变化并更新数据库。所以,我只需要一些 ajax 来实现第三种方法(第一个问题)。你知道怎么做,所以当数据库条目更改时,图像会自动更改(ajax)?仍然想知道二维问题。
    • @AlekseiChepovoi - ajax 就像在用户不做任何事情的情况下刷新图像一样?真的有两个选择——定期轮询或 websockets,比如 nodejs。至于第二个问题,我不知道——你在什么浏览器上测试过它?也许只有一些浏览器的行为很奇怪,我确定 IE 在缓存方面确实如此...... 哎呀,抱歉,我帮不上什么忙
    【解决方案2】:

    如果我理解正确,您所追求的是无限缓存,并依赖于通过更改资源的实际 url 来使缓存无效。

    在这种情况下,我相信实际的实现要简单得多,并且不需要手动处理标头。

    最终,关键是能够通过如下 URL 加载图像:

    http://someDomain/product/image/1?v={something}
    

    “1”是productId,并指定某种版本标识符(“v”)。

    关键是构建该 url,其中 v 的值取决于图像的最后修改(您可能应该将其与图像或产品一起存储)。您可能可以散列修改日期,并使用它。

    如果你下次构建 url 时,最后一次修改的日期是相同的,你会得到相同的哈希值,因此渲染和以前一样的 url,浏览器会从缓存中加载它而不请求任何东西来自服务器。

    一旦图像更新,修改日期发生变化,您的代码将生成一个不同的 url,这将迫使浏览器再次请求它。

    然后你只需将OutputCache属性应用到Action,配置为缓存在客户端(无需指定VaryByParam),你应该设置。

    【讨论】:

    • 我将图像存储在文件流中,所以我认为我无法将修改日期与图像一起存储,我该如何获取它?我使用了 File.GetLastWriteTime 但它不起作用:由于某种原因(即使我不使用 Last-Modified 过滤器实现)LastModified 标头包含“最后访问”日期而不是“最后修改”日期。跨度>
    • 你的意思是在实际的文件系统上?好吧,如果您有足够的权限,GetLastWriteTime 应该可以工作。如果您调试代码,您会看到 GetLastWriteTime 的值无效?另一种选择可能是存储图像上传日期以及一些关联实体,例如产品。这也可以。
    • 是的,实际的文件流。在上面的代码中,我将此修改日期写入 Last-Modified 标头。但是每次我重新加载页面时,我在请求中得到的 If-Modified-Since 等于上一个响应中的 Last-Modified?!这可能是什么原因? p.s.我将在相关实体中为修改日期创建附加属性,但是这种奇怪的 Last-Modified 行为让我非常困扰和恼火:)
    • 老实说,我只想去掉那个动作过滤器。我不知道如果您实施方法 3 会有什么价值。
    • 我是否需要手动构建 url,或者我可以将版本作为参数传递给我的 GetImage() 操作方法?你能提供一些代码吗?
    【解决方案3】:

    您可以使用带有输出缓存的 VaryByCustom 选项来实现此目的,而无需使用自定义属性。像这样更改您的方法代码:

    [HttpGet, OutputCache(Duration = 3600, 
        Location = OutputCacheLocation.Client, 
        VaryByCustom = "imagedate")]
    public FilePathResult GetImage(int productId)
    { // some code }
    

    然后将以下代码添加到您的 Global.asax:

        public override string GetVaryByCustomString(System.Web.HttpContext context, string custom)
        {
            if (custom.ToLower() == "imagedate")
            {
                return System.IO.File.GetLastWriteTime(Server.MapPath("~/Images/my-image.png")).ToString();
            }
            else
            {
                return base.GetVaryByCustomString(context, custom);
            }
        }
    

    当图像文件的时间戳改变时,GetVaryByCustomString 方法的返回值会改变,这将导致 ASP.NET 重新加载图像而不是使用缓存的值。

    更多详情请参阅http://msdn.microsoft.com/en-us/library/aa478965.aspx

    【讨论】:

      【解决方案4】:

      最初使用this 答案,但由于某种原因,当图像被修改时,它不会在客户端更新,而是显示图像的缓存版本。

      所以解决方案是使用版本控制,这是您的第三个选项,只需从您的产品图像数据库中添加 LastUpdated 日期时间字段

      动作方法

          [HttpGet]
          [OutputCache(
          Duration = 7200,
          VaryByParam = "productId;lastUpdated",
          Location = OutputCacheLocation.Client)]
          public ActionResult GetImage(string productId, string lastUpdated)
          {
              var dir = Server.MapPath("~/productimages/");
              var path = Path.Combine(dir, productId + ".jpg");
              return base.File(path, "image/jpeg");
          }
      

      在视图中

      <img src="@Url.Action("GetImage", "Home", new { productId = "test-product-100", 
      lastUpdated =Model.LastUpdated })" />
      

      想法来自this 帖子。

      回答迟了,但希望能帮助别人。

      【讨论】:

        猜你喜欢
        • 2010-12-06
        • 2014-03-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-09-20
        • 2016-06-24
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多