41

I have recently come across the Last-Modified Header.

  • How and where can I include it in MVC?
  • What are the advantages of including it?

I want an example how last modified header can be included in an mvc project, for static pages and database queries as well?

Is it different from outputcache, if yes how?

Basically, I want the browser to clear the cache and display the latest data or pages automatically, without the need for the user to do a refresh or clearing the cache.

learning
  • 10,375
  • 33
  • 84
  • 152
  • What exactly still isn't working? `Microsoft.Practices.EnterpriseLibrary.Caching.Expirations` shouldn't *really* have anything to do with whether an end-user needs to refresh or not. – Kevin Stricker Oct 26 '12 at 00:50
  • @mootinator, the Microsoft.Practice library was mentioned just to tell that I was using that. I have explained in the question that I want an example of how to use it in an mvc project, retrieveing data from a database, maybe sqlserver or anything else. – learning Oct 29 '12 at 06:08
  • @learning I deleted my new answer. I was thinking that Last-Modified header is not being used by OutputCache filter and just came to know it is being used when you set the Location as Client, so my initial answer seems to be valid. – VJAI Oct 30 '12 at 11:13
  • @learning If I get free time I'll try a sample for implementing caching for dynamic pages mean-while why can't you try a small sample specified in the link. – VJAI Oct 30 '12 at 12:38
  • @Mark, with the output cache, it doesn`t clear the cache unless the time set expires. What I want is it clears the cache if there is a something new. – learning Oct 31 '12 at 06:35
  • Your edited question is imho too broad. It would require an essay to give you a proper answer to it. – jgauffin Oct 31 '12 at 10:55
  • @learning I've spent quite time on experimenting OutputCache attribute with SqlCacheDependency. According to my analysis (well not 100% sure) the SqlCacheDependency works only when we use the cache location as server and I'll post a separate question on this, will update you. – VJAI Nov 01 '12 at 14:15
  • @learning I included my new answer along with these information. If you still have questions please give your comments there. – VJAI Nov 01 '12 at 15:00
  • @learning SqlCacheDependency is good option but it will create load on the server.. – cracker Apr 12 '14 at 10:47

5 Answers5

50

The Last-Modified is mainly used for caching. It's sent back for resources for which you can track the modification time. The resources doesn't have to be files but anything. for instance pages which are generated from dB information where you have a UpdatedAt column.

Its used in combination with the If-Modified-Since header which each browser sends in the Request (if it has received a Last-Modified header previously).

How and where can I include it in MVC?

Response.AddHeader

What are the advantages of including it?

Enable fine-grained caching for pages which are dynamically generated (for instance you can use your DB field UpdatedAt as the last modified header).

Example

To make everything work you have to do something like this:

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

You might have to specify a format in the DateTime.Parse.

References:

Disclamer: I do not know if ASP.NET/MVC3 supports that you manage Last-Modified by yourself.

Update

You could create an extension method:

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

And then use them like this:

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"));
    }
}
WΩLLE - ˈvɔlə
  • 6,404
  • 6
  • 44
  • 70
jgauffin
  • 95,399
  • 41
  • 227
  • 352
  • Should I include it per action in the controller? – learning May 17 '12 at 11:34
  • I have been trying to test your code sample and i`m stuck on the part var entity = _db.Get(id). I am using model and I am retrieving data from the database to load in the model. How can I do if (!this.IsModified(entity.UpdatedAt)) with a model loaded? – learning Oct 23 '12 at 07:04
  • Your model has to have a property containing the date when it was modified. You also have to change the `CacheExtensions` methods to static and include it's namespace to be able to use the extension methods – jgauffin Oct 23 '12 at 07:39
  • I have not understood, do you have a link from where I can read through or find some examples. I`m new to the concept of caching using last-modified in mvc. – learning Oct 23 '12 at 07:42
  • which part do you not understand? – jgauffin Oct 23 '12 at 07:43
  • It's in your MVC controller, not in the model? – jgauffin Oct 23 '12 at 09:22
  • 1
    No. I did not say that you should put everything in the model. Just the UpdatedAt (or similar) property. – jgauffin Oct 23 '12 at 11:04
  • The following `if (modifiedSince >= updatedAt)` can fail due to conversions to local / universal time, thus the = part in >= fails. I now round to the second as seen [here](http://stackoverflow.com/questions/1004698/how-to-truncate-milliseconds-off-of-a-net-datetime) – Dänu Apr 27 '14 at 17:23
  • @jgauffin You could move the Response.AddHeader stuff to the IsModified Method by using controller.Response.AddHeader – vipero07 Sep 01 '15 at 05:55
  • Someone calling that method would not expect it to modify the state. But with a rename it would work. – jgauffin Sep 01 '15 at 06:22
19


UPDATE: Check my new answer


How and where can I include it in MVC?

The built-in OutputCache filter does the job for you and it uses those headers for caching. The OuputCache filter uses the Last-Modified header when you set the Location as Client or ServerAndClient.

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

What are the advantages of including it?

Leveraging client-side caching with conditional cache flush

I want an example how last modified header can be included in an mvc project, for static pages and database queries as well?

This link contains enough information to try out a sample. For static pages like html, images IIS will take care of setting/checking the Last-Modified header and it uses the file's last modified date. For database queries you can go for setting the SqlDependency in the OutputCache.

Is it different for outputcache, if yes how? When do I need to include Last-Modified Header and when to use outputcache?

OutputCache is an action filter used for implementing caching mechanism in ASP.NET MVC. There are different ways you could perform caching using OutputCache: client-side caching, server-side caching. Last-Modified header is one way to accomplish caching in the client-side. OutputCache filter uses it when you set the Location as Client.

If you go for client-side caching (Last-Modified or ETag) the browser cache will automatically get updated in subsequent request and you don't need to do F5.

VJAI
  • 29,899
  • 20
  • 93
  • 155
  • 1
    I just checked the source code for the attribute and it do not use those headers. – jgauffin May 15 '12 at 12:05
  • @jqauffin I looked into the code but I'm not sure how all the things works internally. If those headers are not used by the OutputCache filter then the better idea would be extending the filter and override the necessary methods. Correct me if I'm wrong. – VJAI May 15 '12 at 13:27
  • You can't use the fields in your DB if the control is moved to the attribute. – jgauffin May 15 '12 at 13:31
  • I agree with you in specific cases. But if the Last-Modified is going to be based upon the database tables then the SqlCacheDependency won't work? (I'm just curious to know the right approach) – VJAI May 15 '12 at 13:37
  • That's not the topic. You said that `OutputCache` uses LastModified. I say that it's not. Either prove your answer or update it to say that it's incorrect. For instance, use chrome dev tools to look at the HTTP result for an action which uses `OutputCache`. – jgauffin May 15 '12 at 14:05
  • @jqauffin, I updated the answer. Thanks for the clarification. – VJAI May 15 '12 at 14:18
  • @Mark thanks for the explanation but it still doesn`t reply my question. Return me my 50+ :-) – learning May 17 '12 at 09:14
  • Tell me how I can return back? I don't see any options in SO. – VJAI May 21 '12 at 06:08
  • From, http://blog.stevensanderson.com/2008/10/15/partial-output-caching-in-aspnet-mvc/, It doesn’t attempt to cache and replay HTTP headers, so it’s not suitable for caching action methods that issue redirections. – learning Oct 29 '12 at 06:26
  • @jqauffin OutputCache uses the Last-Modified header. You can see that by setting the Location = OutputCacheLocation.Client. You may not see that in the code because it still uses the old page infrastructure for delivering the headers. – VJAI Oct 30 '12 at 10:35
  • @jqauffin I think I don't need to prove to you. You can see yourself in the chrome dev tools by setting the Location = OutputCacheLocation.Client in the OutputCache filter and seeing the response headers – VJAI Oct 30 '12 at 10:40
  • @Mark, is it valid for any other browser in any other versions? – learning Oct 30 '12 at 11:34
  • @Mark, is it by using only OutputCacheLocation.Client or any other parameter have to be set as well? What if the stylesheet was changed, will it clear the cache and reload everything without the user refreshing the page? If the data in sql has been changed, will that too be cleared and reloaded automatically? – learning Oct 30 '12 at 11:37
  • @learning Theoretically when the data in the database changes the server should deliver a fresh copy with the new Last-Modified header set and this should happen only if the SqlCacheDependency is set. The requests to the style-sheets/images are separate and they are not part of this action request. The attached link contains enough information to try out a small sample. – VJAI Oct 30 '12 at 12:10
16

Last-Modified vs. OutputCache

The OutputCache attribute controls output caching on your IIS WebServer. This is a vendor specific server feature (see Configure IIS 7 Output Caching). I also suggest to read Cache Exploration in ASP.NET MVC3 if you are interested in the powerful capabilities of this technology.

Last-Modified response header and it's counterpart If-Modified-Since request header are representatives of the validation cache concept (section cache control). These headers are part of the HTTP protocol and specified in rfc4229

OutputCache and validation are not exclusive, you can combine it.

What caching scenario makes me happy?

As usual: it depends.

Configuring a 5 second OutputCache on a 100 hits/second page would drastically reduce the load. Using OutputCache, 499 out of 500 hits can be served from cache (and do not cost db roundtrip, calculations, rendering).

When I have to serve rarely changes immediately, then the validation scenario could save a lot of bandwith. Specially when you serve large content compared to a lean 304 status message. However, changes are adopted immediately since every request validates for changes in the source.

Last-Modified attribute implementation sample

Based on my experience I would recommend to implement the validation scenario (last modified) as an action filter attribute. (Btw: Here's an other caching scenario implemented as an attribute)

Static content from file

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

Dynamic content sample

[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; }
}

The LastModifiedCache attribute

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

How to enable global LastModified suppport

You can add the LastModifiedCache attribute to the RegisterGlobalFilters section of your global.asax.cs to globally enable this type of caching in your mvc project.

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    ...
    filters.Add(new LastModifiedCacheAttribute());
    ...
}
Community
  • 1
  • 1
Lukas Winzenried
  • 1,869
  • 1
  • 14
  • 21
  • 1
    that`s so helpful and so elegantly done. Thank you very much. I have one more question, In this case you are loading an html file, let`s say I have a css file that has to be loaded. Do I need to include the filter attribute everywhere or once it is loaded, it will automatically reflects in all other pages shown(generated by other actions)? – learning Nov 06 '12 at 07:44
  • 1
    you can use the attribute on actions (caching only for this action), on controllers (caching for all actions of decorated controller) or even register it globally (see addition in my answer) – Lukas Winzenried Nov 06 '12 at 09:03
  • That `LastModifiedCacheAttribute` is a very smart approach to a cross-cutting problem. I could easily see it being welcome in the MvcContrib project. – Paul Turner Jul 18 '13 at 10:19
  • i want to update dynamic data in page when database updates happens , so if i use `[LastModifiedCache]` for dynamic model i need `ILastModifiedDate` interface to be implemented in all my `View Models` ? or should i use Page level caching with `SqlDependency` ? which is good ? – Shaiju T Mar 16 '16 at 10:53
4

Note that outputcache isn't your only option here, and in fact you may well not want to handle last-modified in the way it does. To clarify a few options:

Option 1 - use [OutputCache]

In this case the framework will cache the response body according to whatever duration you specify. It will serve it with Last-Modified set to the current time, and max-age set to the time remaining until the original cache duration expires. If a client sends a request with If-Modified-Since then the framework will correctly return a 304. Once the cached response expires then the Last-Modified date will be updated each time a new response is cached.

  • Pros: caching happens at the controller level (so can work for partial content or the same cached content on different end URLs). You have better control over cacheability - e.g. HttpCacheability.ServerAndPrivate allows your server to cache the content, but not intermediate proxies.
  • Cons: you have no control over last-modified. When your cache expires all clients will need to re-download the content even if it hasn't actually changed

Option 2 - specify settings on Response.Cache

asp.net has another layer of caching outside of the outputcacheattribute in the form of System.Web.OutputCacheModule which all requests pass through. This behaves like an HTTP cache immediately in front of your application. So if you set sensible cache headers without applying the OutputCacheAttribute then your response will be cached here instead. For example:

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

Based on the above then the outputcachemodule would cache your content and any requests to the same URL would be served from the cache. Requests with If-Modified-Since would get 304s. (You could use ETags equally). When your cache expires the next request will hit your app normally, but if you know the content hasn't changed you can return the same Last-Modified or ETag as you did previously. Once this next response has been cached then subsequent clients will then be able to extend their cache lifetimes without re-downloading the content

  • Pros: If you have a meaningful way of determining last-modified or ETag then you have full control over it and can reduce the number of duplicate downloads.
  • Cons: Caching is only at the request/URL level. Only works if you're happy to set cache-control: public

Although this option reduces the likelihood of unnecessary content downloads, it doesn't eliminate it - the first request after the (server) cache expires will be served normally and result in a 200, even if a 304 would have been appropriate. That's probably for the best though, since it enables the cache to grab a fresh copy of the response body which it would have discarded when it expired previously, and hence future requests can be served straight from cache. I believe that HTTP caches can in theory be cleverer than this and use 304s to extend their own cache lifetime, but the asp.net one appears not to support that.

(Edited to replace SetMaxAge with SetExpires in the code above - seems IIS/asp.net won't respect max-age headers unless you also SetSlidingExpiration(true) but that setting appears to prevent the caching we want)

Gareth
  • 1,043
  • 9
  • 6
1

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!

VJAI
  • 29,899
  • 20
  • 93
  • 155
  • thank you for all the explanation. Now i`m a bit confused with your previous answer. Previously, you mentioned the outputcache. I have come across the outputcache, sql dependency... Is the example you`ve given me here, not doing the same task as outputcache sql dependency? When should I include Last-modified and when should I include output chache(that might not be the question, but i`m confused) – learning Oct 30 '12 at 07:01
  • @learning I can't just reply as a comment, I'll update the answer. – VJAI Oct 30 '12 at 10:20
  • thank you very much for all the explanation. You mentioned something like... 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... I remember once having done changes to the css file, but I needed to refresh the page for the effect to take place? Why? Do I need to do any other changes to make the IIS take care of that? – learning Nov 06 '12 at 08:01
  • @learning For static resources along with the Last-Modified header the server also may set the Expires header. In that case, the browser don't issue a request still the cache got expired and so you have to press F5 to force browser to get the latest files. – VJAI Nov 06 '12 at 09:18