在asp.net mvc 3中使用上次修改的标头和OutputCacheAttribute进行客户端缓存

在asp.net mvc 3中使用上次修改的标头和OutputCacheAttribute进行客户端缓存,asp.net,asp.net-mvc,asp.net-mvc-3,caching,browser-cache,Asp.net,Asp.net Mvc,Asp.net Mvc 3,Caching,Browser Cache,已编辑 [HttpGet] [OutputCache( Duration = 7200, VaryByParam = "productId;lastUpdated", Location = OutputCacheLocation.Client)] public ActionResult GetImage(string productId, string lastUpdated) { var dir = Server.MapP

已编辑

    [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");
    }
我想在客户机上缓存图像,并且知道在MVC3中有不同的方法来实现这一点:(如果我错了,请纠正我)

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

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

3) 还有另一种方法(在我的例子中,我被告知正确的方法,因为图像将很少更改…无论如何,我需要完全实现这一点):将修改的日期添加到图像url,并将缓存设置为永久(1年或更长)。如果图像已更改,则应发送带有新版本的新url

代码如下:

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

问题:

<img src="@Url.Action("GetImage", "Home", new { productId = "test-product-100", 
lastUpdated =Model.LastUpdated })" />
1)如何实现第三种方法,以便浏览器将从客户端缓存中获取图像(并且不会在每次需要图像时向服务器发送响应),除非图像被修改?
编辑:实际代码将不胜感激

2)在上述代码中,第一个图像请求的时间被写入最后一次修改(不知道为什么)。如何将文件的修改日期写入上次修改?

编辑:这个问题与第二种方法有关。另外,如果我只在客户端缓存并使用
Last Modified
实现,则只有在按下
F5
时,我才会获得
304 Not Modified
状态。如果我重新输入相同的url,我将得到
200 OK
。如果我在客户端缓存而不使用上次修改的
。这是怎么解释的呢?

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

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

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

http://someDomain/product/image/1?v={something}
“1”是productId,并指定某种版本标识符(“v”)

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

如果下次构建url时,上次修改的日期相同,则将获得相同的哈希值,因此呈现与以前相同的url,浏览器将从缓存加载该url,而无需向服务器请求任何内容

一旦图像更新,并且修改日期更改,您的代码将生成不同的url,这将迫使浏览器再次请求


然后,您只需将
OutputCache
属性应用于配置为在客户端缓存的操作(无需指定
VaryByParam
),就可以进行设置。

您可以使用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重新加载映像,而不是使用缓存的值


有关更多详细信息,请参阅。

您可以研究使用ETags(),这是我阅读您的问题时想到的第一件事

您还可以在此处查看:

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

因此,解决方案是使用版本控制,这是您的第三个选项,只需从产品图像数据库中添加LastUpdated datetime字段即可

行动方法

    [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 })" />

这个想法来自《邮报》


回答很晚,但希望能帮助一些人。

我将图像存储在文件流中,所以我想我不能用图像存储修改后的日期,我如何获得它?我使用了File.GetLastWriteTime,但它不起作用:出于某种原因(即使我没有使用上次修改的筛选器实现),LastModified标头包含“上次访问”日期而不是“上次修改”日期。你是说在实际的文件系统上?如果你有足够的权限,GetLastWriteTime应该可以工作。如果调试代码,会看到GetLastWriteTime的无效值?另一个选项可能是存储图像上载日期,以及一些相关实体,例如产品。这也可以。是的,实际的文件流。在上面的代码中,我将此修改日期写入上次修改的标题中。但是每次我重新加载页面时,如果在请求中修改,那么我得到的页面是否等于上次响应中修改的页面?!这是什么原因呢?p、 我将为相关实体中的修改日期创建附加属性,但这个奇怪的L