views:

111

answers:

6

I have a dynamically-generated javascript file that I want to ensure is NEVER cached by the browser. My current method is to simply append a (new) guid to the url in the script tag on each page view.

For example:

<script type="text/javascript" src="/js/dynamic.js?rand=979F861A-C487-4AA8-8BD6-84E7988BD460"></script>

My question is...is this the best way to accomplish my goal?

For reference, I am using ASP.NET MVC 2 and the javascript file is being generated as a result of a controller action.

+5  A: 

The method you have works just fine, an alternative would be to use DateTime.UtcNow.Ticks. You can of course use cache control headers, but if you want to be absolutely sure, the method you have is the way to go.

Though, if you're generating the file dynamically anyway, and need to include it in the page...any reason for not just sticking the JavaScript in the page? Since you want it to never cache, this will save a round-trip for the client.

Nick Craver
@Nick: I suppose I could include it in the body of the page, but it's much easier to reference it as external as it's shared across several pages/master pages.
DanP
@DanP - Is render partial isn't an option here? It seems like it'd be the equivalent amount of work, but I could be missing other details specific to your site.
Nick Craver
@Nick: On second thought; wouldn't this method not ensure that the javascript was unique for every page view (eg. the browser cached the page containing the javascript).
DanP
@DanP - Not unless the browser is caching the entire page...in which case you'd be having problems now, since it'd also do the same thing with a GUID and not request that JavaScript again...since it hasn't seen a script with a new GUID yet :)
Nick Craver
I would also recommend to include it into the page. That will make it impossible to cache for the browser and save a request.
Kau-Boy
duh...yes..thanks. Too early; not enough coffee :)
DanP
**"/js/dynamic.js" should be served with no-caching headers.** Rewriting the URL with a cache-buster for each requests works. But if the script is served with incorrect headers, then it potentially takes up space in the browser cache and any intermediary caches, space intended for cache-able content. The best and most RFC compliant solution is to *both* ensure the JS is served with proper no-cache headers, and to add a cachebuster query string to each emitted URL.
Jesper Mortensen
@jesper.mortensen - I agree with all those points...in this case though, if it's per-request anyway, a RenderPartial and placing the script block right in the page seems more appropriate, and eliminates a round-trip to the server :) So...the best solution *in this case* is a different approach entirely, at least IMO.
Nick Craver
@Nick Craver: Yep, I agree with you. Maybe I misread your answer a little, I read it with an emphasis on the first part, not the question in the second part. One (semi-pedantic) addition; if the script is inlined and *must* never be cached, then the HTML document containing the inlined JS must be served with no-cache HTTP headers.
Jesper Mortensen
A: 

Yes that is good. I personally do the same but I use timestamp instead which makes sure that it is always unique.

Sarfraz
A timestamp is *much* more likely to be non-unique than a GUID.
Michael Borgwardt
@Michael Borgwardt: I never said it is better, I just showed what i do personally :)
Sarfraz
@sAc: Yes a timestamp is a good option as well, I was more concerned with my method than the datatype of the random param. Thanks for the input
DanP
@Michael - You're thinking of the wrong terms...it doesn't need to be unique for *everyone*, it just needs to be unique *per-user*, they all fetch JavaScript files independently. All we want is to ensure a request happens to the server, the timestamp doesn't affect the actual file generation.
Nick Craver
@Micahel: Than explain me how a sinlge user can get the exact same timestamp twice requesting one page? I never saw users browsing sites in milliseconds. And there are even millisecond timestamps :) But a random GUID can't be unique ad the randomization is never perfekt and as it is random, it can be repeated.
Kau-Boy
If we really want to split hairs, I'm actually using a ShortGuid: http://www.singular.co.nz/blog/archive/2007/12/20/shortguid-a-shorter-and-url-friendly-guid-in-c-sharp.aspx
DanP
A: 

You might consider using HTTP Etags

bjg
Actually, I specifically strip all Etags out on our server; so this wouldn't be an option in my specific case
DanP
A: 

That's a usual way how people do it. You can also change the filename for each requests to be completely sure.

something like <script src="{unique string}.js">

dwich
If he did this...he couldn't catch the request for the controller appropriately :)
Nick Craver
@Nick Not unless I was REALLY lucky anyways ;)
DanP
Interesting thought though..I wonder if it would be possible to create a unique route per-request in MVC 2.
DanP
+2  A: 

You could create a custom action attribute:

public class NoCacheAttribute : ActionFilterAttribute
{  
    public override void OnResultExecuting(ResultExecutingContext filterContext)
    {
        var cache = filterContext.HttpContext.Response.Cache;
        cache.SetExpires(DateTime.UtcNow.AddDays(-1));
        cache.SetValidUntilExpires(false);
        cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
        cache.SetCacheability(HttpCacheability.NoCache);
        cache.SetNoStore();
        base.OnResultExecuting(filterContext);
    }
}

And then decorate the controller action that generates javascript with this attribute:

[NoCache]
public ActionResult MyAction()
{
    ...
}

In this case you no longer need the random string you are appending.

Darin Dimitrov
@Darin: very nice...I wonder if using this in conjunction with my random param would be a good idea? eg. Safe and a little more intention-revealing when reading my controller code.
DanP
A: 

Use the cache control HTTP headers in the response.

Cache-Control: no-cache

(This is therefore purely a server setting, no coding needed.)

Alternately, as you are using ASP.NET MVC, use routing to ignore the last element of the URL, in the HTML have

<script type="text/javascript" src="/js/dynamic.js/979F861A-C487-4AA8-8BD6-84E7988BD460"></script>

and use the MVC route to ignore the suffix (i.e. make every HTML page served have a different script URL, not just a query parameter).

Richard