MVC中的最后修改标头

时间:2012-05-08 11:58:15

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

我最近遇到了Last-Modified Header。

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

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

它与outputcache有什么不同,如果是的话怎么样?

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

5 个答案:

答案 0 :(得分:46)

Last-Modified主要用于缓存。它会被回送给您可以跟踪修改时间的资源。资源不一定是文件,而是任何东西。例如,根据您有UpdatedAt列的dB信息生成的页面。

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

  

我如何以及在何处将其包含在MVC中?

Response.AddHeader

  

包含它有什么好处?

为动态生成的页面启用细粒度缓存(例如,您可以使用数据库字段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"));
    }
}

答案 1 :(得分:15)


更新:检查我的新答案


我如何以及在何处将其包含在MVC中?

内置的OutputCache过滤器为您完成工作,并使用这些标头进行缓存。 将OuputCache设置为Last-ModifiedLocation时,Client过滤器会使用ServerAndClient标题。

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

包含它有什么好处?

利用条件缓存刷新来利用客户端缓存

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

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

对于outputcache是​​不同的,如果是的话怎么样?我什么时候需要包含Last-Modified Header以及何时使用outputcache?

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

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

答案 2 :(得分:13)

Last-Modified vs. OutputCache

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

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

中指定

OutputCache和验证不是独占的,您可以将它组合起来。

哪种缓存方案让我开心?

像往常一样:这取决于。

在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 suppport

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

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    ...
    filters.Add(new LastModifiedCacheAttribute());
    ...
}

答案 3 :(得分:4)

请注意,outputcache不是你唯一的选择,实际上你可能不想像它那样处理最后修改。澄清几个选项:

选项1 - 使用[OutputCache]

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

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

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

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

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

基于上述内容,outputcachemodule将缓存您的内容,并且将从缓存中提供对同一URL的任何请求。使用If-Modified-Since的请求将获得304s。 (你可以平等地使用ETags)。当您的缓存过期时,下一个请求将正常访问您的应用,但如果您知道内容未更改,则可以返回与之前相同的Last-Modified或ETag。一旦缓存了下一个响应,那么后续客户端将能够延长其缓存生命周期而无需重新下载内容

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

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

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

答案 4 :(得分:1)

这是我对缓存和OutputCache进行一些研究之后的第二个答案。

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

  

包含它有什么好处?

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

Last-Modified标题告诉浏览器最后何时修改了资源资源可以是静态文件动态创建的视图。每当浏览器发出对该资源的请求时,它都会检查服务器&#34;嘿,我已经有了对此请求的响应,并且它的Last-Modified日期是如此...查看用户已经累了...如果你回复304,我很高兴使用我的缓存中的响应,请快速发送你的新回复&#34; 。 (请注意,浏览器会将服务器先前返回的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?

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

您是否真的需要在所有操作中实施最后修改机制

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

关于ETag

的观点

虽然问题是关于Last-Modified标题,但在点击发布你的答案按钮之前,我应该告诉ETag。与Last-Modified(依赖于日期时间)相比,标头ETag标头(依赖于哈希值)更准确地确定浏览器中的缓存响应是否是新鲜的但是它可能不太复杂实行。 IIS还包含ETag标头以及静态资源的Last-Modified标头。在实施任何此机制之前,请先google并查看是否有任何库可帮助您解决问题!