MVC4 Script Bundles Caching issue
Asked Answered
A

3

8

We have an MVS application where we are bundling the javascript code using Bundle class ( don't do minification).

Bundling just works fine, but when we run the application, Cache value is set to Cache-Control:no-cache and at the same time every time we refresh the page the request always has a 200 OK. This means the js is not getting cached on client even though nothing was changed.

Also is there a way to verify if the bundled js is built dynamically or getting it from server cache?

Thanks

Apothegm answered 14/4, 2014 at 5:49 Comment(2)
Not sure this is helpful, but you might want to try getcassette.net instead. I just think it works great and is very easy to use.Edina
Thanks Daniel, But I wanted to know why Bundling is not working in a MVC 4 web app.Apothegm
A
2

The problem appears to be with the Microsoft.AspNet.Web.Optimization NuGet package. After downgrading the version from 1.3.0 to 1.1.0, everything seems to be working fine.

Link to blog post on codeplex which mentioned the same issue

Apothegm answered 15/4, 2014 at 4:26 Comment(1)
Just to clarify, 1.1.0 works, 1.1.1 seems to be where the bug is introduced. The bug specifically affects bundles deployed to a different server. The bug cannot be seen if you run locally.Freiman
F
9

I was seeing the same behavior as described in codeplex link that mentions the issue:

i.e. if I visit these URLs in the following order then the behaviour is -

bundle.css?v=1234 : no-cache
bundle.css : public
bundle.css?v=1234 : public

I decided to dig into the System.Web.Optimization source code a bit to see what was going on. On the Bundle class, there is a private method setting headers, and it appears to be falling into this code:

if (noCache) {
    cachePolicy.SetCacheability(HttpCacheability.NoCache);
}

The noCache variable is set via a parameter. The calling method in this case is setting it:

// Set to no-cache if the version requested does not match
bool noCache = false;
var request = context.HttpContext.Request;
if (request != null) {
    string queryVersion = request.QueryString.Get(VersionQueryString);
        if (queryVersion != null && bundleResponse.GetContentHashCode() != queryVersion) {
                noCache = true;
    }
}

Long story short, we had switched to using Azure CDN for our bundles and changed the version query string parameter to something like ?v=1.0.0.0 based on the assembly version (similar to on this question). The bundle code is comparing "1.0.0.0" with the SHA256 hash code of the bundle content and flagging the bundle for no-cache as a result.

I resolved this by updating our query string to match the content hash.

Unfortunately the access level for the GetContentHashCode method is marked internal, so it was necessary to replicate the implementation. I ended up creating a class that inherited from Bundle so that it could apply the version number to the CdnPath as a transform:

public class ProxiedCdnBundle : Bundle
{
    private readonly string _cdnHost;

    public ProxiedCdnBundle(string virtualPath, string cdnHost = "")
        : base(virtualPath)
    {
        _cdnHost = cdnHost;
    }

    public override BundleResponse ApplyTransforms(BundleContext context, string bundleContent, IEnumerable<BundleFile> bundleFiles)
    {
        var response = base.ApplyTransforms(context, bundleContent, bundleFiles);

        if (context.BundleCollection.UseCdn && !String.IsNullOrWhiteSpace(_cdnHost))
        {
            string path = System.Web.VirtualPathUtility.ToAbsolute(context.BundleVirtualPath);
            base.CdnPath = string.Format("{0}{1}?v={2}", _cdnHost, path, GetBundleHash(response));
        }

        return response;
    }
    

    private static string GetBundleHash(BundleResponse response)
    {
        using (var hashAlgorithm = CreateHashAlgorithm())
        {
            return HttpServerUtility.UrlTokenEncode(hashAlgorithm.ComputeHash(Encoding.Unicode.GetBytes(response.Content)));
        }
    }

    private static SHA256 CreateHashAlgorithm()
    {
        if (CryptoConfig.AllowOnlyFipsAlgorithms)
        {
            return new SHA256CryptoServiceProvider();
        }

        return new SHA256Managed();
    }
}
Flutter answered 14/7, 2016 at 18:3 Comment(4)
how did you update the query string to match the content hash? did you recompile the source or just change how the URL was generated?Moneyer
@Moneyer It required a code change to our bundle config code. I've updated the answer to include that piece as well. Hope that helps.Flutter
you sir are a gentleman and a scholar. this was a really tough nut to crack due to undocumented weirdness in the bundling engine. i am surprised not more people have noticed this. both the fact that version gets removed when enabling cdn url but also how the response headers are set to nocache if the hash is not correct, which means home made versioning query string appending wont work.Internode
You could have also done that by changing query string parameter from 'v' to something else i.e. 'version'Caundra
A
2

The problem appears to be with the Microsoft.AspNet.Web.Optimization NuGet package. After downgrading the version from 1.3.0 to 1.1.0, everything seems to be working fine.

Link to blog post on codeplex which mentioned the same issue

Apothegm answered 15/4, 2014 at 4:26 Comment(1)
Just to clarify, 1.1.0 works, 1.1.1 seems to be where the bug is introduced. The bug specifically affects bundles deployed to a different server. The bug cannot be seen if you run locally.Freiman
P
2

As the answers above did not help me (unsure of the consequences), I found a workaround for this issue.

The problem is, as stated already here, that, when you send a ?v on the query string and the value does not match the actual hash, it will return no-cache.

Not sending anything at all is not an option (cache may never expire). Sending a cache busting parameter is not an option either. If you do so and you have multiple instances, you may cache the wrong value during the deploy (if you don't remove from the load balancer the old instances).

To fix this issue, just set UseCdn to false and change the following during the bundle configuration:

Scripts.DefaultTagFormat = string.Format(@"<script src=""{0}{{0}}""></script>", CdnRoot);

Hope, I've helped.

Pulvinus answered 15/3, 2017 at 20:21 Comment(3)
This solved problems with Cache-Control: private, following instructions at Integrate ASP.NET bundling and minification with Azure CDN, thank you!Uprising
@walter Could you explain a bit more about this deployment issue. I have this problem right now but it only happens on deployed versions, my local dev version always works fine (even running on deploy version configs). Could this hashing issue be somehow related? ThanksFreiman
Never mind, it is the damn Optimization package from 1.1.1Freiman

© 2022 - 2024 — McMap. All rights reserved.