views:

330

answers:

5

I have noticed that when updating my web content files (in this case, a silverlight XAP file) the browser does not detect that the file has been updated, and continues to reads the locally cached file. These files will only be updated rarely, so reading from the cached temporary internet files should occur most of the time.

My question is whether there is a programmatic way to ensure that files are downloaded from the website, rather than read from the local cache, but only when these files have changed? Is there a widely accepted process for handling this scenario? I don't want every user of this web product to have to delete their temporary internet files when an update is installed.

These files will only be updated during the execution of an installer, so can I possibly programmatically set something to ensure that this will happen?

+2  A: 

You can append a random number or datetime value to the querystring.

The problem occurs most of the time an AJAX call is made. To avoid taking values from the cache you can build a querystring.

Eg:

var xmlPath = "www.domain.com?vers="+new Date().getTime();+"";
rahul
This is not a good solution as the version number would change every second. In essence it would never be cached at all. Please see my solution below for an appropriate solution.
hobodave
A: 

Hi Lucas. Your question is a little vague, as I don't know what tools you are using. I'll give examples in PHP.

You'll want to set the Cache-Control header to specify the max age that the file can be cached. The value is specified in seconds.

e.g.

<?php
header("Cache-Control","max-age=86400");

You would set this header before sending the XAP file to the user. 86400 seconds = 1 day.

Another alternative is to use different filenames for each version of the file. You can do this one of two ways.

Embedding a version # in the file name:

some_file_1_1.xap
some_file_1_2.xap

Or, my preference is to append the version number to the filename as a query string:

some_file.xap?20090719
some_file.xap?20090720

You would reference these files with the appropriate "version" number in your HTML, and when the version # changes, the browser considers this a change in filename and will force redownloading. You can use whatever you want for the "version" string - options may include: revision #, last modified datestring, etc.

If you decide to go with changing the filename on every revision, then I'd suggest using "far future" caching. Ensuring the file is cached for an insanely long time will decrease the load on your server. You can do this as shown above with PHP, and here's a few examples to do it with mod_expires in Apache 2.0.

ExpiresActive on
ExpiresByType application/x-silverlight-app “access plus 5 years”
FileETag none
hobodave
This approach works for small, frequently accessed resources such as JS files. However it does require that all references be adorned in this way and updating the resource requires all references to be updated as well. XAPs are generally much larger than JS files and don't see the same frequent request traffic as JS files. Also there is a significant startup lag with silverlight hence just marking the resource as expires immediately works well with XAPs since the latency of the small 304 round-trip is dwarfed by the Silverlight startup time.
AnthonyWJones
This approach works for any file. I fail to see how you draw the conclusion that because XAP files are much larger, having them uncached is a good thing? Think that one through. Save the internet, and his webserver some bandwidth. The OP didn't ask about how to do this in as low latency a way as possible. He even explicitly stated that the files change rarely, and only when he releases a new version of his application.
hobodave
@hobodave: The approach I recommend does NOT leave the XAP uncached, not does it require the XAP to be refetch everytime. It will only cause the XAP the actually be fetched if it is different from the a cached one. I suggest you have a look the HTTP 1.1 spec, read up on If-Modified-Since header and the 304 Unmodifed response status code.
AnthonyWJones
A: 

I think you can do something like this as well: Obviously do this in a base class that each page will use. So create like a PageBase which inherits System.Web.UI.Page and have all your code behinds inherit from PageBase.

or do it in the Master Page.

protected void Page_Init(object sender, EventArgs e) {
    Response.Cache.SetExpires(DateTime.UtcNow.AddMinutes(-1));
    Response.Cache.SetCacheability(HttpCacheability.NoCache);
    Response.Cache.SetNoStore();
}
Jack Marchetti
Thats fine if you generate XAP content (basically a Zip file) from an ASPX page, probably not something you really want to do and is not what phoenix is doing.
AnthonyWJones
+1  A: 

Lots of programatic solutions so far. The solution however is to simply configure the ClientBin direcotry (or whatever folder you store your XAPs in) on the server.

Assuming IIS you need to explicitly specify the the ClientBin folder Expires Immediately. This will cause the correct cache configuration headers to be sent when the XAP is retrieved. In turn the browser will attempt to retireve the XAP each time its needed but in the vast majority of cases will simply get a 304 Unmodified response and it can continue to use its cached copy.

My guess is you are seeing the classic IE heuristic problem. In the absence of any cache configuration headers IE makes up its own mind whether to even bother to re-request a resource according to its own internal algorithms. By ensuring the correct expiry headers are sent IE will honor the servers instructions.

Edit

It seems that I need to make the operation of this approach clearer. This approach does not leave the XAP resource uncached and in need of fetching everytime its needed.

By specifying the Expire Immediately feature in IIS we get these headers in the Response:-

HTTP/1.1 200 OK
Cache-Control: no-cache
Content-Length: 22359
Content-Type: application/octet-stream
Last-Modified: Tue, 21 Jul 2009 11:59:28 GMT
ETag: "fe734cb3fa9ca1:1352"

This does not prevent the XAP from being cached it merely indicates the browser may not used the cached XAP without first requesting it from the server. Note the Last-Modified and ETag headers.

A subsequent request looks like this:-

GET /clientBin/SomeApp.xap HTTP/1.1
If-Modified-Since: Tue, 21 Jul 2009 11:59:28 GMT
If-None-Match: "fe734cb3fa9ca1:135a"
Host: myhost.com

The response is:-

HTTP/1.1 304 Not Modified
Cache-Control: no-cache
Last-Modified: Tue, 21 Jul 2009 11:59:28 GMT
Tag: "fe734cb3fa9ca1:135a"

This response carries no entity body, it gives the browser permission to go ahead and use the existing XAP in the cache.

If the XAP is large then it is possible that the browser will not actually cache it with the Cache-Control specified as no-cache. Hence it may actually be better to be more explicit.

Instead of using the Expires Immediately box use configure the Cache-Control header using the Custom header list. Specify:-

Cache-Control: max-age=0

This will cause the browser to cache large XAPs whilst immediately expirying them.

AnthonyWJones
As stated in my comment above. This is a poor solution. Not caching a large file will cause unnecessary burden on his webserver and his users machines. Save the world's bandwidth, please.
hobodave
@hobodave: I think you misunderstand how caching works. Specifying expiration immediately does not stop the resource being cached. It just forces the browser to request the resource with an If-Modified-Since header. The server can respond with 304 Not Modified response which does not contain any content.
AnthonyWJones