42

我最近遇到了 Last-Modified Header。

  • 我如何以及在哪里可以将它包含在 MVC 中?
  • 包含它有什么好处?

我想要一个示例,对于静态页面和数据库查询,如何将最后修改的标头包含在 mvc 项目中?

它与输出缓存有什么不同,如果是的话如何?

基本上,我希望浏览器清除缓存并自动显示最新的数据或页面,而不需要用户进行刷新或清除缓存。

4

5 回答 5

50

主要Last-Modified用于缓存。它被发回以获取您可以跟踪其修改时间的资源。资源不必是文件,而是任何东西。例如,从您拥有一UpdatedAt列的 dB 信息生成的页面。

它与每个浏览器在请求中发送的标头结合使用If-Modified-Since(如果它之前已收到Last-Modified标头)。

我如何以及在哪里可以将它包含在 MVC 中?

Response.AddHeader

包含它有什么好处?

为动态生成的页面启用细粒度缓存(例如,您可以使用 DB 字段UpdatedAt作为最后修改的标题)。

例子

为了使一切正常,您必须执行以下操作:

public class YourController : Controller
{
    public ActionResult MyPage(string id)
    {
        var entity = _db.Get(id);
        var headerValue = Request.Headers["If-Modified-Since"];
        if (headerValue != null)
        {
            var modifiedSince = DateTime.Parse(headerValue).ToLocalTime();
            if (modifiedSince >= entity.UpdatedAt)
            {
                return new HttpStatusCodeResult(304, "Page has not been modified");
            }
        }

        // page has been changed.
        // generate a view ...

        // .. and set last modified in the date format specified in the HTTP rfc.
        Response.AddHeader("Last-Modified", entity.UpdatedAt.ToUniversalTime().ToString("R"));
    }
}

您可能必须在 DateTime.Parse 中指定格式。

参考:

Disclamer : 我不知道 ASP.NET/MVC3 是否支持你Last-Modified自己管理的。

更新

您可以创建一个扩展方法:

public static class CacheExtensions
{
    public static bool IsModified(this Controller controller, DateTime updatedAt)
    {
        var headerValue = controller.Request.Headers['If-Modified-Since'];
        if (headerValue != null)
        {
            var modifiedSince = DateTime.Parse(headerValue).ToLocalTime();
            if (modifiedSince >= updatedAt)
            {
                return false;
            }
        }

        return true;
    }

    public static ActionResult NotModified(this Controller controller)
    {
        return new HttpStatusCodeResult(304, "Page has not been modified");
    }   
}

然后像这样使用它们:

public class YourController : Controller
{
    public ActionResult MyPage(string id)
    {
        var entity = _db.Get(id);
        if (!this.IsModified(entity.UpdatedAt))
            return this.NotModified();

        // page has been changed.
        // generate a view ...

        // .. and set last modified in the date format specified in the HTTP rfc.
        Response.AddHeader("Last-Modified", entity.UpdatedAt.ToUniversalTime().ToString("R"));
    }
}
于 2012-05-11T05:38:48.150 回答
19


更新:检查我的新答案


我如何以及在哪里可以将它包含在 MVC 中?

内置OutputCache过滤器为您完成这项工作,并使用这些标头进行缓存。当您设置as或时,OuputCache过滤器使用Last-Modified标题。LocationClientServerAndClient

[OutputCache(Duration = 60, Location = "Client")]
public ViewResult PleaseCacheMe()
{
    return View();
}

包含它有什么好处?

通过条件缓存刷新利用客户端缓存

我想要一个示例,对于静态页面和数据库查询,如何将最后修改的标头包含在 mvc 项目中?

链接包含足够的信息来试用示例。对于像 html 这样的静态页面,图像 IIS 将负责设置/检查Last-Modified标题,并使用文件的最后修改日期。对于数据库查询,您可以SqlDependencyOutputCache.

输出缓存有什么不同,如果是的话如何?何时需要包含 Last-Modified Header 以及何时使用 outputcache?

OutputCache是一个动作过滤器,用于在 ASP.NET MVC 中实现缓存机制。您可以使用不同的方式执行缓存OutputCache:客户端缓存、服务器端缓存。Last-Modifiedheader 是在客户端完成缓存的一种方法。OutputCache过滤器在您设置为时使用LocationClient

如果您使用客户端缓存(Last-ModifiedETag),浏览器缓存将在后续请求中自动更新,您无需执行 F5。

于 2012-05-12T17:33:18.777 回答
16

Last-Modified 与 OutputCache

OutputCache属性控制 IIS WebServer 上的输出缓存。这是供应商特定的服务器功能(请参阅配置 IIS 7 输出缓存)。如果您对该技术的强大功能感兴趣,我还建议您阅读ASP.NET MVC3 中的缓存探索。

Last-Modified响应头及其对应的If-Modified-Since请求头是验证缓存概念(部分缓存控制)的代表。这些标头是 HTTP 协议的一部分,在rfc4229中指定

OutputCache 和validation 不是独占的,可以结合使用。

什么缓存场景让我开心?

像往常一样:这取决于。

在 100 次点击/秒的页面上配置 5 秒的 OutputCache 将大大减少负载。使用 OutputCache,可以从缓存中提供 500 个命中中的 499 个(并且不花费 db 往返、计算、渲染)。

当我必须很少立即更改服务时,验证场景可以节省大量带宽。特别是当您提供与精益 304 状态消息相比的大型内容时。但是,由于每个请求都会验证源中的更改,因此会立即采用更改。

Last-Modified 属性实现示例

根据我的经验,我建议将验证场景(上次修改)实现为操作过滤器属性。(顺便说一句:是作为属性实现的另一个缓存场景)

来自文件的静态内容

[LastModifiedCache]
public ActionResult Static()
{
    return File("c:\data\static.html", "text/html");
}

动态内容示例

[LastModifiedCache]
public ActionResult Dynamic(int dynamicId)
{
    // get data from your backend (db, cache ...)
    var model = new DynamicModel{
        Id = dynamivId,
        LastModifiedDate = DateTime.Today
    };
    return View(model);
}

public interface ILastModifiedDate
{
    DateTime LastModifiedDate { get; }
}

public class DynamicModel : ILastModifiedDate
{
    public DateTime LastModifiedDate { get; set; }
}

LastModifiedCache 属性

public class LastModifiedCacheAttribute : ActionFilterAttribute 
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        if (filterContext.Result is FilePathResult)
        {
            // static content is served from file in my example
            // the last file write time is taken as modification date
            var result = (FilePathResult) filterContext.Result;
            DateTime lastModify = new FileInfo(result.FileName).LastWriteTime;
            
            if (!HasModification(filterContext.RequestContext, lastModify))
                filterContext.Result = NotModified(filterContext.RequestContext, lastModify);
            SetLastModifiedDate(filterContext.RequestContext, lastModify);
        }

        if (filterContext.Controller.ViewData.Model is HomeController.ILastModifiedDate)
        {
            // dynamic content assumes the ILastModifiedDate interface to be implemented in the model
            var modifyInterface = (HomeController.ILastModifiedDate)filterContext.Controller.ViewData.Model;
            DateTime lastModify = modifyInterface.LastModifiedDate;

            if (!HasModification(filterContext.RequestContext, lastModify))
                filterContext.Result = NotModified(filterContext.RequestContext, lastModify);
            filterContext.RequestContext.HttpContext.Response.Cache.SetLastModified(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");
    }
}

如何启用全局 LastModified 支持

您可以将 LastModifiedCache 属性添加到 global.asax.cs 的 RegisterGlobalFilters 部分,以在您的 mvc 项目中全局启用这种类型的缓存。

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    ...
    filters.Add(new LastModifiedCacheAttribute());
    ...
}
于 2012-10-30T22:24:10.820 回答
5

请注意, outputcache 不是您在这里唯一的选择,实际上您可能不想以它的方式处理 last-modified 。澄清几个选项:

选项 1 - 使用 [OutputCache]

在这种情况下,框架将根据您指定的任何持续时间缓存响应正文。它将 Last-Modified 设置为当前时间,并将 max-age 设置为原始缓存持续时间到期之前的剩余时间。如果客户端使用 If-Modified-Since 发送请求,则框架将正确返回 304。一旦缓存的响应过期,则每次缓存新响应时都会更新 Last-Modified 日期。

  • 优点:缓存发生在控制器级别(因此可以用于部分内容或不同最终 URL 上的相同缓存内容)。您可以更好地控制可缓存性 - 例如,HttpCacheability.ServerAndPrivate 允许您的服务器缓存内容,但不能缓存中间代理。
  • 缺点:您无法控制最后修改。当您的缓存过期时,所有客户端都需要重新下载内容,即使它实际上并没有改变

选项 2 - 在 Response.Cache 上指定设置

asp.net 在 outputcacheattribute 之外还有另一层缓存,以 System.Web.OutputCacheModule 的形式,所有请求都通过它。这就像在您的应用程序前面的 HTTP 缓存一样。因此,如果您在不应用 OutputCacheAttribute 的情况下设置了合理的缓存标头,那么您的响应将被缓存在这里。例如:

Response.Cache.SetLastModified(lastModifiedDate); Response.Cache.SetCacheability(HttpCacheability.Public); Response.Cache.SetExpires(DateTime.Now + timespan);

基于上述情况,输出缓存模块将缓存您的内容,并且将从缓存中提供对同一 URL 的任何请求。带有 If-Modified-Since 的请求会得到 304。(您可以同样使用 ETags)。当您的缓存过期时,下一个请求将正常访问您的应用程序,但如果您知道内容没有更改,您可以返回与之前相同的 Last-Modified 或 ETag。缓存下一个响应后,后续客户端将能够延长其缓存寿命,而无需重新下载内容

  • 优点:如果您有一种有意义的方式来确定最后修改或 ETag,那么您可以完全控制它,并且可以减少重复下载的数量。
  • 缺点:缓存仅在请求/URL 级别。仅当您乐于设置 cache-control: public 时才有效

尽管此选项减少了不必要的内容下载的可能性,但它并没有消除它 - (服务器)缓存过期后的第一个请求将正常提供并导致 200,即使 304 本来是合适的。不过,这可能是最好的,因为它使缓存能够获取响应主体的新副本,该副本在之前过期时会被丢弃,因此可以直接从缓存中提供未来的请求。我相信 HTTP 缓存理论上可以比这更聪明,并使用 304 来延长自己的缓存寿命,但 asp.net 似乎不支持这一点。

(编辑以在上面的代码中用 SetExpires 替换 SetMaxAge - 似乎 IIS/asp.net 不会尊重 max-age 标头,除非您也 SetSlidingExpiration(true) 但该设置似乎阻止了我们想要的缓存)

于 2015-02-11T20:36:09.413 回答
1

这是我对缓存和OutputCache.

我先回答你的第二个问题。

包含它有什么好处?

浏览器缓存从服务器返回的响应。缓存主要由三个标头控制:Cache-ControlLast-ModifiedExpires还有其他类似的ETag也可以使用)。

Last-Modified头告诉浏览器资源最后何时被修改。资源可以是静态文件或动态创建的视图。每当浏览器对该资源发出请求时,它都会与服务器进行检查“嘿,我已经对此请求做出了响应,并且它的Last-Modified日期是某某……看到用户已经很累了……如果您返回 304 我”我很高兴使用我缓存中的响应,否则请快速发送您的新响应”。(请注意,浏览器将Last-Modified服务器先前返回的值传递到名为 的新标头中If-Modified-Since

理想情况下,服务器应该从标头中读取值,If-Modified-Since并且必须检查当前的修改日期,如果它们相同,那么它应该返回304(未修改),或者它应该返回资源的新副本,再次传递当前修改日期标题Last-Modified

优点是浏览器缓存。通过利用浏览器缓存,服务器可以避免创建重复响应,并且如果浏览器中的缓存响应看起来很旧,它还可以返回新响应。最终目的是节省时间

我如何以及在哪里可以将它包含在 MVC 中?

对于图像、html 文件等静态资源,您无需担心设置方式位置,因为IIS 负责这项工作。IIS 使用文件的最后修改日期作为标头值。Last-Modified

对于通过 MVC 操作返回的 html 内容等动态页面,如何确定Last-Modified标头值?动态驱动的页面主要是数据驱动的,我们有责任决定之前返回的响应是否过时。

假设您有一个博客,并且您有一个页面,无论您是否显示文章的详细信息(而不是任何其他详细信息),那么页面的版本由上次修改日期或创建日期(如果文章尚未修改)决定文章。因此,您必须在提供视图的相应操作中执行@jgauffin回答的相同工作。

您在评论中问过我应该在控制器中的每个操作中包含它吗?

如果您能够从动作中抽象出从数据库中读取最后修改日期的逻辑,那么您可以通过动作过滤器完成这项工作,避免在整个动作中重复代码。问题是你将如何从动作中抽象出细节?就像将表/列名称传递给属性?你必须弄清楚!

举个例子..

[LastModifiedCacheFilter(Table = "tblArticles", Column = "last_modified")]
public ViewResult Post(int postId)
{
   var post = ... get the post from database using the postId
   return View(post);
}

下面显示的实现的伪代码(意味着我还没有测试过:)LastModifiedCacheFilterAttribute使用表/列来读取最后修改的日期,但它也可以是其他一些方式。这个想法是在OnActionExecuting我们进行检查并返回 304 的方法中(如果缓存仍然是新的),并且在OnResultExecuted我们正在读取/设置最新修改日期的方法中。

public class LastModifiedCacheFilterAttribute : ActionFilterAttribute
{
    // Could be some other things instead of Table/Column
    public string Table { get; set; }
    public string Column { get; set; }    

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
      // var lastModified = read the value from the passed Column/Table and set it here 

      var ifModifiedSinceHeader = filterContext.RequestContext.HttpContext.Request.Headers["If-Modified-Since"];

      if (!String.IsNullOrEmpty(ifModifiedSinceHeader))
      {
        var modifiedSince = DateTime.Parse(ifModifiedSinceHeader).ToLocalTime();
        if (modifiedSince >= lastModified)
        {
          filterContext.Result = new EmptyResult();
          filterContext.RequestContext.HttpContext.Response.Cache.SetLastModified(lastModified.ToUniversalTime());
          filterContext.RequestContext.HttpContext.Response.StatusCode = 304;
        }
      }

      base.OnActionExecuting(filterContext);
    }

    public override void OnResultExecuted(ResultExecutedContext filterContext)
    {
      // var lastModified = read the value from the passed Column/Table and set it herefilterContext.RequestContext.HttpContext.Response.Cache.SetLastModified(lastModified.ToUniversalTime());
      base.OnResultExecuted(filterContext);
    }
}

为什么不能输出缓存属性?

根据我的分析,OutputCache属性不使用Last-Modified缓存机制。另一件事是它使用旧的页面缓存机制,使其难以定制/扩展。

您真的需要在所有操作中实现最后修改机制吗?

真的不需要。您可以对需要更多时间来创建此类响应的操作实施最后修改机制,并且需要更多时间将响应沿线路传输并到达浏览器。在其他情况下,我觉得这只是在所有操作中实施的开销,而且您必须在这样做之前衡量收益。另一个要点是,在许多情况下,页面的版本不仅由单个表列决定,还可能由许多其他因素决定,在这些情况下,实现这一点可能更复杂!

关于一点ETag

虽然问题是关于标题的,但在单击“发布您的答案”按钮之前,Last-Modified我应该先说一下。与(依赖于日期时间)头部头部(依赖于哈希值)相比,在确定浏览器中的缓存响应是否新鲜方面更准确,但实现起来可能有点复杂。IIS 还包括标题以及静态资源的标题。在实施任何这种机制之前,谷歌看看是否有任何图书馆可以帮助你!ETagLast-ModifiedETagETagLast-Modified

于 2012-10-30T05:02:19.160 回答