Edited
I want to cache images on the client and know that there are different ways to do it in mvc 3: (correct me if I'm wrong)
1) You can use OutputCacheAttribute
which works with the help of Expires
http header. But it will return 304 Not Modified
unless the time expire (even if the image was changed).
2) To avoid displaing stale images You can use Last-Modified
http header (with OutputCacheAttribute
). In this case the browser sends the request to the server with If-Modified-Since
http header. On the server You verify whether the object is still valid or not and if it is You just return Last-Modified
http header (and the browser takes image from the local cache); if the object was modified You return it with 200 OK
status.
So, the browser needs to send the request to the server each time before taking image from it's own cache. Here is the example -
3) There is another way (as I was told the right way in my case, cause the images will change very rarely... anyway, I need to implement exactly this): To add modified date to the image url and set caching with Expires
for the eternity (1 year or more). If the image have changed You should send new url with new version.
Here is the code:
public class LastModifiedCacheAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext.Result is FilePathResult)
{
var result = (FilePathResult)filterContext.Result;
var lastModify = File.GetLastWriteTime(result.FileName);
if (!HasModification(filterContext.RequestContext, lastModify))
filterContext.Result = NotModified(filterContext.RequestContext, lastModify);
SetLastModifiedDate(filterContext.RequestContext, 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");
}
}
And I registered the LastModifiedCacheAttribute
in Global.asax and applied the following OutputCacheAttribute to my action method.
[HttpGet, OutputCache(Duration = 3600, Location = OutputCacheLocation.Client, VaryByParam = "productId")]
public FilePathResult GetImage(int productId)
{ // some code }
If I use the code above seems like the browser doesn't send requests to the server, instead it just take images from the cache unless the duration is not ended. (When I change the image the browser doesn't display new version)
Questions:
1) How to implement the third approach, so that the browser will take the images from client cache (and will not send the response to the server each time it wants the image) unless the image was modified?
edited: the actual code will be appreciated.
2) In the code above the time of the first image request is written to the Last-Modified (don't know why). How to write the modification date of the file into Last-Modified?
edited: this question relates to second approach. Also, if I cache only on the client and use Last-Modified
implementation I get 304 Not Modified
status only if I press F5
. If I reenter the same url I will get 200 OK
. If I cache on a client without using Last-Modified
it will always return 200 OK
no matter what. How could this be explained?