MVC中的Last-Modified头

我最近遇到最后修改标题。

  • 如何以及在哪里可以包含在MVC中?
  • 包括它的优点是什么?

我想要一个例子如何最后修改头可以包括在一个MVC项目,静态页面和数据库查询以及?

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

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

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

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

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

Response.AddHeader

包括它的优点是什么?

为dynamic生成的页面启用细粒度caching(例如,您可以使用数据库字段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中指定格式。

参考文献:

  • HTTP状态码
  • HTTP标头

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

更新

你可以创build一个扩展方法:

 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")); } } 

更新:检查我的新答案


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

内置的OutputCachefilter为您完成这项工作,并使用这些标头进行caching。 当您将Location设置为ClientServerAndClient时, OuputCache筛选器使用Last-Modified标头。

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

包括它的优点是什么?

利用有条件caching刷新的客户端caching

我想要一个例子如何最后修改头可以包括在一个MVC项目,静态页面和数据库查询以及?

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

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

OutputCache是一个用于在ASP.NET MVC中实现caching机制的动作filter。 使用OutputCache可以执行caching的方法有很多种:客户端caching和服务器端caching。 Last-Modified头是在客户端完成caching的一种方法。 当您将Location设置为Client时, OutputCache筛选器使用它。

如果您使用客户端caching( Last-ModifiedETag ),则浏览器caching将在随后的请求中自动更新,您不需要执行F5。

最后修改与输出caching

OutputCache属性控制IIS WebServer上的输出caching。 这是特定于供应商的服务器function(请参阅configurationIIS 7输出caching )。 如果您对此技术的强大function感兴趣,我还build议阅读ASP.NET MVC3中的Cache Exploration 。

Last-Modified响应头和它的对应的If-Modified-Since请求头是validationcaching概念(节caching控制 )的代表。 这些头文件是HTTP协议的一部分,并在rfc4229中指定

OutputCache和validation不是唯一的,你可以把它结合起来。

什么cachingscheme让我开心?

像往常一样:这取决于。

以100次/秒的页面configuration5秒OutputCache将大大减less负载。 使用OutputCache,500个命中中的499个可以从caching中提供(并且不花费数据库往返,计算,渲染)。

当我不得不立即提供更改时,validationscheme可以节省很多带宽。 特别是当您服务较大的内容与精益304状态消息相比。 但是,由于每个请求都会validation源中的更改,因此会立即采取更改。

Last-Modified属性实现示例

基于我的经验,我会build议实施validationscheme(上次修改)作为一个行为filter属性。 (顺便说一句: 这是一个其他的cachingscheme作为一个属性实现)

来自文件的静态内容

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

dynamic内容示例

 [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项目中全局启用此类caching。

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

请注意,outputcache并不是你唯一的select,事实上,你可能不想处理最后修改的方式。 澄清几个选项:

选项1 – 使用[OutputCache]

在这种情况下,框架将根据指定的持续时间来caching响应主体。 它将在Last-Modified设置为当前时间的情况下提供服务,max-age设置为原始caching持续时间到期的剩余时间。 如果客户端使用If-Modified-Since发送请求,则框架将正确地返回304.一旦caching的响应过期,则每当新的响应被caching时,Last-Modifieddate将被更新。

  • 优点:高速caching发生在控制器级别(所以可以用于部分内容或不同的最终URL上相同的caching内容)。 您可以更好地控制caching – 例如HttpCacheability.ServerAndPrivate允许您的服务器caching内容,但不是中间代理。
  • 缺点:你无法控制上次修改。 当caching过期时,所有的客户端将需要重新下载内容,即使它没有真正改变

选项2 – 指定Response.Cache上的设置

asp.net在输出caching属性之外还有另外一层以System.Web.OutputCacheModule的forms存在的所有请求都经过的caching。 这就像应用程序前面的HTTPcaching一样。 所以,如果你设置了明智的caching头,而不应用OutputCacheAttribute,那么你的响应将被caching在这里。 例如:

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

根据上面的内容,outputcachemodule会caching你的内容,任何对同一个URL的请求都会从caching中提供。 If-Modified-Since的请求将获得304s。 (你可以同样使用ETags)。 当你的caching过期时,下一个请求会正常打到你的应用,但是如果你知道内容没有改变,你可以像以前一样返回相同的Last-Modified或者ETag。 一旦这个下一个响应被caching了,那么后续的客户端将能够延长它们的caching生命周期而不需要重新下载内容

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

虽然这个选项减less了不必要的内容下载的可能性,但它并没有消除它 – 在(服务器)caching过期后的第一个请求将正常服务,结果是200,即使304是合适的。 这可能是最好的,因为它使得caching能够获得一个新的应答主体副本,这个副本在以前过期时会被丢弃,因此未来的请求可以直接从caching中提供。 我相信HTTPcaching理论上可以比这个更聪明,并使用304s来延长自己的caching生命周期,但asp.net似乎不支持。

(在上面的代码中用SetExpires代替SetMaxAge – 似乎IIS / asp.net不会考虑max-age头文件,除非你也SetSlidingExpiration(true),但是这个设置似乎阻止了我们想要的caching)

这是我对caching和OutputCache做了一些相当研究之后的第二个回答。

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

包括它的优点是什么?

浏览器caching从服务器返回的响应。 caching主要由三个标题Cache-ControlCache-ControlLast-ModifiedExpires (还有其他像ETag也来玩)。

Last-Modified标题告诉浏览器资源何时被修改。 资源可以是静态文件dynamic创build的视图 。 每当浏览器发出请求的资源,它与服务器检查“嗨,我已经有这个请求的回应,它的Last-Modifieddate是如此这般…看到用户已经累了…如果你返回一个304我很乐意使用我的caching中的响应,否则请快速发送新的响应“ 。 (请注意,浏览器传递服务器在之前返回的Last-Modified值中的一个名为If-Modified-Since的新标头)

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

浏览器caching的好处是 通过利用浏览器caching服务器可以避免创build一个重复的响应,也可以返回一个新的响应,如果在浏览器中的caching响应看起来像旧的。 最终的目标是节省时间

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

在静态资源(如图像,html文件等)的情况下,您无需担心如何设置“ 如何”“位置”,因为IIS负责处理该作业 。 IIS使用该文件的上次修改date作为Last-Modified标头值。

在通过MVC动作返回的HTML内容等dynamic页面的情况下,如何确定Last-Modified标题值? dynamic驱动页面大部分是数据驱动的,我们有责任决定之前返回的响应是否陈旧。

比方说,你有一个博客,你有一个页面,无论你显示的文章的细节(没有任何其他细节),那么页面的版本是由最后修改date或创builddate(如果文章尚未修改)文章。 所以你必须做相同的工作@jgauffin回答相应的行动 ,提供的观点。

你在评论中已经问过我应该在控制器中包含它吗?

如果能够从操作中抽象出从数据库中读取最后修改date的逻辑,那么可以通过操作filter来完成工作,避免在整个操作中复制代码。 问题是你如何将这些细节从行为中抽象出来? 像传递表/列名称的属性? 你必须弄清楚!

举个例子..

 [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实现的伪代码(意思是我没有testing过这个:)使用Table / Column来读取最后修改的date,但是也可以用其他方法。 这个想法是在OnActionExecuting方法,我们正在做检查,并返回一个304(如果caching仍然是新鲜的),并在OnResultExecuted方法,我们正在阅读/设置最新的修改date。

 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-Modifiedcaching机制。 另一件事是它使用旧的页面caching机制,很难定制/扩展。

你真的需要在你的所有行动中实施最后修改的机制吗?

真的不需要。 您可以将最后修改的机制实施到需要更多时间来创build此类响应的操作 ,并且需要更多时间将响应传送到networking并到达浏览器。 在其他情况下,我觉得这只是一个贯穿所有行动的额外开支在这样做之前,你也要衡量好处 。 另一个要点是,在许多情况下,页面的版本不是由一个表格列决定的,它可能是许多其他的东西,在这种情况下,实现它可能会更复杂!

关于ETag的一点

虽然问题是关于Last-Modified标题,我应该在点击Post Your Answerbutton之前告诉一些关于ETag 。 与Last-Modified (依赖于date时间)相比,头部ETag头(依赖于哈希值)在确定浏览器中的caching响应是否新鲜时更准确,但实现起来可能并不复杂。 IIS还包含ETag标题以及静态资源的Last-Modified标题。 在执行这个机制之前谷歌出来看看是否有任何图书馆可以帮助你!