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中?
内置的OutputCache
filter为您完成这项工作,并使用这些标头进行caching。 当您将Location
设置为Client
或ServerAndClient
时, 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-Modified
或ETag
),则浏览器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-Control
: Cache-Control
, Last-Modified
和Expires
(还有其他像ETag
也来玩)。
Last-Modified
标题告诉浏览器资源何时被修改。 资源可以是静态文件或dynamic创build的视图 。 每当浏览器发出请求的资源,它与服务器检查“嗨,我已经有这个请求的回应,它的Last-Modified
date是如此这般…看到用户已经累了…如果你返回一个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-Modified
caching机制。 另一件事是它使用旧的页面caching机制,很难定制/扩展。
你真的需要在你的所有行动中实施最后修改的机制吗?
真的不需要。 您可以将最后修改的机制实施到需要更多时间来创build此类响应的操作 ,并且需要更多时间将响应传送到networking并到达浏览器。 在其他情况下,我觉得这只是一个贯穿所有行动的额外开支,在这样做之前,你也要衡量好处 。 另一个要点是,在许多情况下,页面的版本不是由一个表格列决定的,它可能是许多其他的东西,在这种情况下,实现它可能会更复杂!
关于ETag
的一点
虽然问题是关于Last-Modified
标题,我应该在点击Post Your Answerbutton之前告诉一些关于ETag
。 与Last-Modified
(依赖于date时间)相比,头部ETag
头(依赖于哈希值)在确定浏览器中的caching响应是否新鲜时更准确,但实现起来可能并不复杂。 IIS还包含ETag
标题以及静态资源的Last-Modified
标题。 在执行这个机制之前谷歌出来看看是否有任何图书馆可以帮助你!