This is my second answer after doing some quite research on caching and OutputCache
.
Let me answer your second question first.
What are the advantages of including it?
Browser caches the responses returned from the server. The caching is controlled by mainly three headers: Cache-Control
, Last-Modified
and Expires
(there are others like ETag
also comes to play).
The Last-Modified
header tells the browser when does the resource has been modified at last. The resource could be either static file or dynamically created view. Whenever the browser makes the request for that resource it checks with the server "Hey, I already have a response for this request and it's Last-Modified
date is so and so.. see the user is already tired... if you return a 304 I'm glad to use the response from my cache else please send your new response quick". (Note that the browser passes the Last-Modified
value returned previously by the server in a new header called If-Modified-Since
)
Ideally the server should read the value from the If-Modified-Since
header and has to check with the current modified date and if they are same then it should return 304 (NOT MODIFIED) or it should return the new copy of the resource again passing the current modified date in the Last-Modified
header.
The advantage is browser caching. By leveraging the browser caching the server can avoid creating a duplicate response and also it can return a new response if the cached response in the browser looks like old. The ultimate goal is save the time.
How and where can I include it in MVC?
In the case of static resources like images, html files and others you don't need to worry about setting How and Where because IIS takes care of that job. IIS uses the file's last modified date as the Last-Modified
header value.
In the case of dynamic pages like a html content returned through an MVC action, how you can determine the Last-Modified
header value? The dynamic driven pages are mostly data driven and it's our responsibility to decide whether the response returned previously is stale or not.
Let's say you have a blog and you have a page whether you display the details of an article (not any other details) then the page's version is decided by the last modified date or created date (if the article is not modified yet) of the article. So you have to do the same work answered by @jgauffin in the corresponding action that delivers the view.
You have asked in the comment Should I include it per action in the controller?
If you could able to abstract away the logic of reading the last modified date from database from the actions then you could accomplish the job through an action filter avoiding duplicating the code throughout actions. The question is how you are going to abstract the details away from the actions? Like passing the table/column names to the attribute? You have to figure it out!
As an example..
[LastModifiedCacheFilter(Table = "tblArticles", Column = "last_modified")]
public ViewResult Post(int postId)
{
var post = ... get the post from database using the postId
return View(post);
}
The pseudo code (means I haven't tested this :) of the LastModifiedCacheFilterAttribute
implementation shown below uses Table/Column to read the last modified date but it could be some other ways as well. The idea is in the OnActionExecuting
method we are doing the check and returning a 304 (if the cache is still fresh) and in the OnResultExecuted
method we are reading/setting the latest modified 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);
}
}
Why can't OutputCache attribute?
As per my analysis, OutputCache
attribute not uses Last-Modified
caching mechanism. The other thing is it uses the old page caching mechanism making it difficult to customize/extend.
Do you really need to implement the last-modified mechanism in all your actions?
Really not required. You could implement the last-modified mechanism to the actions that takes more time to create such a response and it takes more time to travel the response down the wire and reach the browser. In other cases I feel it just an overhead implementing throughout all the actions and also you have to measure out the benefits before doing so. The other main point is, in many cases the version of the page is not just decided by a single table column it could be by many other things and in those cases it may be more complicated to implement this!
A point about ETag
Though the question is about Last-Modified
header I should tell something about ETag
before clicking the Post Your Answer button. Compared to Last-Modified
(which relies on datetime) header ETag
header (relies on a hash value) is more accurate in determining whether the cached response in the browser is fresh or not but it could be little complicated to implement. IIS also includes ETag
header along with the Last-Modified
header for static resources. Before implementing any of this mechanism google out and see whether there is any library out there that helps you out!