我最近遇到了Last-Modified Header。
我想要一个示例,如何在mvc项目中包含最后修改的标头,以及静态页面和数据库查询?
它与outputcache有什么不同,如果是的话怎么样?
基本上,我希望浏览器清除缓存并自动显示最新的数据或页面,而无需用户刷新或清除缓存。
答案 0 :(得分:46)
Last-Modified
主要用于缓存。它会被回送给您可以跟踪修改时间的资源。资源不一定是文件,而是任何东西。例如,根据您有UpdatedAt
列的dB信息生成的页面。
它与每个浏览器在请求中发送的If-Modified-Since
标头结合使用(如果之前已收到Last-Modified
标头)。
我如何以及在何处将其包含在MVC中?
包含它有什么好处?
为动态生成的页面启用细粒度缓存(例如,您可以使用数据库字段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-Modified
或Location
时,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-Modified
或ETag
),浏览器缓存将在后续请求中自动更新,您无需执行F5。
答案 2 :(得分:13)
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状态消息时。但是,由于每个请求都会验证源中的更改,因此会立即采用更改。
根据我的经验,我建议将验证方案(最后修改)实现为操作过滤器属性。 (顺便说一句: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日期。
选项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。一旦缓存了下一个响应,那么后续客户端将能够延长其缓存生命周期而无需重新下载内容
尽管此选项降低了不必要内容下载的可能性,但它并没有消除它 - (服务器)缓存过期后的第一个请求将正常提供并导致200,即使304已经过适当。这可能是最好的,因为它使缓存能够获取响应主体的新副本,它在先前过期时将丢弃,因此未来的请求可以直接从缓存中提供。我相信HTTP缓存在理论上可以比这更聪明,并使用304s来扩展自己的缓存生命周期,但asp.net似乎不支持它。
(编辑在上面的代码中用SetExpires替换SetMaxAge - 似乎IIS / asp.net不会尊重max-age标头,除非你也设置SetSlidingExpiration(true)但该设置似乎阻止了我们想要的缓存)< / p>
答案 4 :(得分:1)
这是我对缓存和OutputCache
进行一些研究之后的第二个答案。
让我先回答你的第二个问题。
包含它有什么好处?
浏览器缓存从服务器返回的响应。缓存主要由三个标题控制:Cache-Control
,Last-Modified
和Expires
(还有其他像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并查看是否有任何库可帮助您解决问题!