【问题标题】:Last-Modified Header in MVCMVC 中的最后修改标头
【发布时间】:2012-05-16 21:48:16
【问题描述】:

我最近遇到了 Last-Modified Header。

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

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

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

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

【问题讨论】:

  • 究竟是什么仍然不起作用? Microsoft.Practices.EnterpriseLibrary.Caching.Expirations 不应该真的与最终用户是否需要刷新有任何关系。
  • @mootinator,提到 Microsoft.Practice 库只是为了告诉我我正在使用它。我已经在问题中解释了我想要一个如何在 mvc 项目中使用它的示例,从数据库中检索数据,可能是 sqlserver 或其他任何东西。
  • @learning 我删除了我的新答案。我在想 OutputCache 过滤器没有使用 Last-Modified 标头,只是在您将 Location 设置为 Client 时才知道它正在使用,所以我最初的回答似乎是有效的。
  • @learning 如果我有空闲时间,我会尝试一个为动态页面实现缓存的示例,而您为什么不能尝试链接中指定的一个小示例。
  • @Mark,使用输出缓存,除非设置的时间到期,否则不会清除缓存。我想要的是如果有新的东西它会清除缓存。

标签: asp.net .net asp.net-mvc asp.net-mvc-3


【解决方案1】:

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

它与每个浏览器在请求中发送的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 中指定格式。

参考资料:

声明:不知道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"));
    }
}

【讨论】:

  • 我应该在控制器中包含每个操作吗?
  • 我一直在尝试测试您的代码示例,但我被困在 var entity = _db.Get(id) 部分。我正在使用模型,并且正在从数据库中检索数据以加载到模型中。如果 (!this.IsModified(entity.UpdatedAt)) 加载了模型,我该怎么办?
  • 您的模型必须具有包含修改日期的属性。您还必须将 CacheExtensions 方法更改为静态并包含它的命名空间才能使用扩展方法
  • 我不明白,你有一个链接,我可以从中阅读或找到一些例子。我对在 mvc 中使用 last-modified 进行缓存的概念很陌生。
  • 没有。我没有说你应该把所有东西都放在模型中。只是 UpdatedAt(或类似的)属性。
【解决方案2】:


更新:检查我的新答案


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

内置的OutputCache 过滤器为您完成这项工作,它使用这些标头进行缓存。 当您将Location 设置为ClientServerAndClient 时,OuputCache 过滤器使用Last-Modified 标头。

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

包含它有什么好处?

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

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

这个link 包含足够的信息来试用样本。对于像 html 这样的静态页面,图像 IIS 将负责设置/检查 Last-Modified 标头,并使用文件的最后修改日期。对于数据库查询,您可以在OutputCache 中设置SqlDependency

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

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

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

【讨论】:

  • 我刚刚检查了属性的源代码,它没有使用那些标题。
  • @jqauffin 我查看了代码,但我不确定所有的东西在内部是​​如何工作的。如果 OutputCache 过滤器不使用这些标头,那么更好的办法是扩展过滤器并覆盖必要的方法。如果我错了,请纠正我。
  • 如果控件移动到属性,您将无法使用数据库中的字段。
  • 在特定情况下我同意你的看法。但是,如果 Last-Modified 将基于数据库表,那么 SqlCacheDependency 将不起作用? (我只是想知道正确的方法)
  • 这不是主题。你说OutputCache 使用 LastModified。我说不是。要么证明你的答案,要么更新它说它是不正确的。例如,使用 chrome 开发工具查看使用 OutputCache 的操作的 HTTP 结果。
【解决方案3】:

Last-Modified 与 OutputCache

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

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

OutputCache和validation不是独占的,可以结合起来。

什么样的缓存场景让我很开心?

像往常一样:这取决于。

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

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

Last-Modified 属性实现示例

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

文件中的静态内容

[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());
    ...
}

【讨论】:

  • thats so helpful and so elegantly done. Thank you very much. I have one more question, In this case you are loading an html file, lets 说我有一个必须加载的 css 文件。我是否需要在任何地方都包含过滤器属性,或者一旦加载,它将自动反映在显示的所有其他页面中(由其他操作生成)?
  • 您可以在操作(仅缓存此操作)、控制器(缓存装饰控制器的所有操作)上使用该属性,甚至可以全局注册它(请参阅我的答案中的补充)
  • LastModifiedCacheAttribute 是解决横切问题的一种非常聪明的方法。我可以很容易地看到它在 MvcContrib 项目中受到欢迎。
  • 我想在数据库更新时更新页面中的动态数据,所以如果我将[LastModifiedCache] 用于动态模型,我需要在我的所有View Models 中实现ILastModifiedDate 接口?还是我应该使用带有SqlDependency 的页面级缓存?哪个好?
【解决方案4】:

请注意,在这里输出缓存不是您唯一的选择,实际上您可能不希望以它的方式处理 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) 但该设置似乎阻止了我们想要的缓存)

【讨论】:

    【解决方案5】:

    这是我在对缓存和OutputCache 做了一些相当研究之后的第二个答案。

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

    包含它有什么好处?

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

    Last-Modified 标头告诉浏览器资源最后何时被修改。 资源可以是静态文件动态创建的视图。每当浏览器向该资源发出请求时,它都会与服务器进行检查您返回 304 我很高兴使用缓存中的响应,否则请快速发送您的新响应”。 (请注意,浏览器将服务器先前返回的 Last-Modified 值传递到名为 If-Modified-Since 的新标头中)

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

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

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

    对于图像、html 文件等静态资源,您无需担心设置 HowWhere,因为 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 标头,但在点击发布您的答案 按钮之前,我应该先介绍一下ETag。与Last-Modified(依赖于日期时间)标头相比,ETag 标头(依赖于哈希值)在确定浏览器中的缓存响应是否新鲜方面更准确,但实现起来可能有点复杂。 IIS 还包括ETag 标头以及用于静态资源的Last-Modified 标头。在实施任何这种机制之前,请先搜索一下是否有任何库可以帮助您!

    【讨论】:

    • 感谢您的所有解释。现在 im a bit confused with your previous answer. Previously, you mentioned the outputcache. I have come across the outputcache, sql dependency... Is the example youve 给了我这里,不做与 outputcache sql 依赖相同的任务?什么时候应该包含 Last-modified 以及什么时候应该包含输出 chache(这可能不是问题,但我很困惑)
    • @learning 我不能只回复评论,我会更新答案。
    • 非常感谢您的所有解释。您提到了类似...对于图像、html 文件等静态资源,您无需担心设置方式和位置,因为 IIS 负责这项工作...我记得曾经对css 文件,但我需要刷新页面才能产生效果?为什么?我是否需要做任何其他更改才能让 IIS 处理这个问题?
    • @learning 对于静态资源以及 Last-Modified 标头,服务器还可以设置 Expires 标头。在这种情况下,浏览器不会发出请求,但缓存已过期,因此您必须按 F5 强制浏览器获取最新文件。
    猜你喜欢
    • 2018-05-12
    • 1970-01-01
    • 2022-12-09
    • 1970-01-01
    • 1970-01-01
    • 2013-02-08
    • 1970-01-01
    • 2012-03-06
    • 2015-01-23
    相关资源
    最近更新 更多