22

On our farm, I'm deploying some JavaScript and CSS files under the _layouts folder.

I'm then referencing them, where it's required using a <SharePoint:ScriptLink> or a <SharePoint:CssRegistration> control :

<SharePoint:CssRegistration runat="server" Name="/_layouts/styles/company/custom.css" /> <SharePoint:ScriptLink runat="server" Name="/_layouts/scripts/company/custom.js" Localizable="false" /> 

This is working fine.

However, these assets are cached in the client browser. When I deploy minor updates, I have to ask users to press Ctrl+F5 to reload without the cache, but it's not an acceptable workaround.

Is there a way to ensure the latest version is loaded, without disabling the cache?

By now, I can see two possible solutions:

  1. version the assets in the file name (custom.1.0.css, custom.1.1.css, ...): I don't like this solution because it requires to update every code that reference the asset
  2. Add a "revision number" to the url. I can trick using this code :

    <SharePoint:CssRegistration runat="server" Name="/_layouts/styles/company/custom.css" id="customCss" /> <SharePoint:ScriptLink runat="server" Name="/_layouts/scripts/company/custom.js" Localizable="false" id="customJs" /> 

    And in the codebehind :

     private static readonly long g_AssemblyTimeStamp = File.GetCreationTime(System.Reflection.Assembly.GetExecutingAssembly().Location).Ticks; protected override void OnInit(EventArgs e) { customJs.Name += "?rev=" + g_AssemblyTimeStamp; customCss.Name += "?rev=" + g_AssemblyTimeStamp; } 

    This adds to the script url something like ?rev=634946193703232026. The browser then sees a new url and reload it, and still cache this file.

    This is working, but still requires some plumbing code to make it works.

Is there any clean solution to meet this requirement?

    5 Answers 5

    11

    The solution is, to get rid of the starting "/" in your Name values.

    JS

    SharePoint will automatically check, if the js file changes and will append a custom query string with a revision number, just like it does with the internal js files, if you check the source of your website. You just have to change the Name in your Scriptlink control like:

    <SharePoint:ScriptLink runat="server" Name="scripts/company/custom.js" Localizable="false" id="customJs" /> 

    This will change the path to your custom.js on the website to s.th. like:

    <script src="/_Layouts/scripts/company/custom.js?rev=HVORLc5FI20n7W90mjha3A%3D%3D"></script> 

    (If you have your JS files under _layouts/1033, you should set localizable to "true" and keep the Name path the same).

    CSS

    For CSS, the relative path starts under "/_layouts/1033/styles", so to get to "/_layouts" and make SharePoint take care of Cache Busting, you should change your CSSRegistration Name to the following

    <SharePoint:CssRegistration runat="server" Name="../../company/custom.css" id="customCss" /> 

    This will change the path to your custom.css on the website to s.th. like:

    <link id="CssRegistration2" rel="stylesheet" type="text/css" href="/_Layouts/1033/styles/../../scripts/company/custom.css?rev=p63%2BuzTeSJc22nVGNZ5zwg%3D%3D"/> 

    (For CSS, I am not sure, if you can use Localizable="False")

    Interesting article: http://community.rightpoint.com/blogs/viewpoint/archive/2012/02/24/cache-busting-with-the-sharepoint-cssregistration-control.aspx

    4
    • After some years of practice, your answer is, with no hesitation, the actual correct answer. Thanks
      – Steve B
      CommentedJan 31, 2017 at 9:20
    • Beware that this doesn't seem to work with SharePoint Online. The _Layouts folder which is used as a root for cache management is hosted on a global domain for all SPO sites static.sharepointonline.com.CommentedFeb 3, 2017 at 5:03
    • @JonathanHolvey: indeed, adding files under the layouts folder is supported only in onpremise scenarios, as it requires access to the file system.
      – Steve B
      CommentedJul 11, 2017 at 10:01
    • Just a follow up on why SP is appending rev key to Name in ScriptLink. It will append it always unless it begins with tilde key ~.CommentedDec 11, 2018 at 11:27
    15

    SPUtility provide us a method called - MakeBrowserCacheSafeLayoutsUrl(string, boolean) and other overload methods.

    Based on last modification this itself generate a MD5 hash code and append it to css or js urls. We don't have to worry about finding version numbers or generating id based on creation date, this is already handled.

    I have used this in my project for handling css caching for custom css. SharePoint internally does same for OOB css which are rendered by CSSLink.

    protected override void OnInit(EventArgs e) { CssRegistration cssRegistration1 = new CssRegistration(); cssRegistration1.After = "corev4.css"; cssRegistration1.ConditionalExpression = "IE 7"; cssRegistration1.Name = SPUtility.MakeBrowserCacheSafeLayoutsUrl("FolderBelowLayouts/Styles/CustomCss.css", false); //will be /_layouts/15/FolderBelowLayouts/Styles/CustomCss.css?rev=... Controls.Add(cssRegistration1); base.OnInit(e); } 

    and for link tags you can directly do:

    <link rel="stylesheet" type="text/css" href="<%=SPUtility.MakeBrowserCacheSafeLayoutsUrl("FolderBelowLayouts/Styles/CustomCss.css", false)%>" /> 

    Refer to: http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.utilities.sputility.makebrowsercachesafelayoutsurl.aspx

    3
    • 3
      I don't think SPUtility.MakeBrowserCacheSafeLayoutsUrl is a good solution. Not a solution at all, really. It does generate ?rev= string after your file, but it does so only once. if you change your file it will not regenerate ?rev= string because ?rev part is stored in a private field in SPUtility.s_layoutsFileHashCache. This cache does not get purged during iisreset and after new wsp is installed. Please, correct me if i'm wrong.CommentedDec 29, 2013 at 22:11
    • 2
      Just to update on Denis' comment (and his question here: sharepoint.stackexchange.com/questions/85984/…) - MakeBrowserCacheSafeLayoutsUrl works as advertised. The Cache is cleared with each IISRESET.
      – Dennis G
      CommentedNov 18, 2014 at 16:43
    • Update: It does work perfectly well. I take my words back. I've been using it for several years already and it seems to be the best choice for avoiding caching problemsCommentedJan 17, 2017 at 15:18
    3

    Steve,

    there is no clean solution for your problem. Browsers cache files by url, you can avoid caching appending in query string unique value per deployment. For example

    _layouts/my_js_file.js?v=<current date> - will be refreshed from cache when day changes

    _layouts/my_js_file.js?v=<GUID> - if guid is generated on every request, this file should never cache

    _layouts/my_js_file.js?v=<product version> - more preferable solution, browser will update cache on every new version that was deployed

    Actually, ScriptLink should take care about this, internally it has a method that appends unique id into query string based on js file content, if content changed, hash is also changed and new unique id is generated.

    I recommend you to read this article about your issue.

    3
    • 2
      I don't understand. You stated that the scriptlink control will add the hash to the url automatically. This is not the case in my case. Is it possible that this does not occurs in subfolders or _layouts? Actually, my deployment is under _layouts/CustomerName/file...
      – Steve B
      CommentedJan 29, 2013 at 14:09
    • @SteveB, if this is not expected behaviour for you, you should go away from ScriptLink and implement your own script control (ScriptLink is sealed, you can't extend it) with your logic.CommentedJan 29, 2013 at 14:47
    • Actually, I have a solution, which is described in my answer. Having to rewrite the script link completely won't be an improvement. Anyways, thanks for the suggestion.
      – Steve B
      CommentedJan 29, 2013 at 14:53
    1

    Not sure if this is a cleaner solution, but a couple of years back I did something similar for an Internet facing publishing site.

    First, we were using SharePoint Server with the publishing infrastructure hence having a "/Style Library/" with major versioning enabled to store assets like images, CSS & JS files.

    Then we created a "PlaceholderAdditionalPageHead" SharePoint delegate control with OnInit code to retrieve the last published major version number of the file in question and append it to the URL like you did.

    This saved us from deploying files to "/_layouts/" and also no GAC deployment as we used an ASCX WebControl. Both were requirements set by higher-ups we couldn't get around.

    Unfortunately, I can't get my hands on the code currently because I don't have a VM handy with the sourcecode repository holding that old project code. Hope it still helps a little bit...

    UPDATE: I forgot to mention that the ASCX WebControl had to be deployed to the server. However, since our solution was part of a larger package we were allowed to deploy the ASCX to the server (otherwise we would have used a DocLib with inline code enabled per web.config). Sorry for missing that part first-hand.

    1
    • 1
      thanks for your post. But I don't any problem to deploy under the layouts directory. In fact, I don't see how changing the deployment location may help to solve my issue (as you said, you had to add some plumbing code)
      – Steve B
      CommentedJan 25, 2013 at 12:58
    0

    I see this is an older question but I stumbled upon it because I wanted to know if the SOD system always added a random key to the end of the files to force them to always reload. Apparently, if you materially modify the file then the SOD system is supposed to trigger the generation of a new key. I haven't found this to be accurate.

    What I did was make a function to generate my own random key. Very simple:

    someNamespace.getScriptKey=function(){ var k=""; for(var i=0;i<12;i++) { var c=Math.floor(Math.random()*25)+97; k+=String.fromCharCode(c); } return k; }; 

    Now, wherever I want to link to a file I just use this type of pattern. If you want to tweak the getScriptKey() function to return a key based on the date or maybe on some constant value that you update whenever you deploy a new version, this is obviously possible and flexible. It is totally transparent; you know exactly how it works.

     var sKey='?'+someNamespace.getScriptKey(); SP.SOD.registerSod('keyA.js','/any/path/to/files/keyA.min.js'+sKey); SP.SOD.registerSod('keyB.js','/siteassets/scripts/keyB.min.js'+sKey); SP.SOD.loadMultiple(['keyA.js','keyB.js'], function(){ console.log('All files loaded'); }); 

      Start asking to get answers

      Find the answer to your question by asking.

      Ask question

      Explore related questions

      See similar questions with these tags.