Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVC 5 bundling and Azure CDN (query string)

I have been following this tutorial: https://azure.microsoft.com/en-us/documentation/articles/cdn-serve-content-from-cdn-in-your-web-application/

Everything had been great until I noticed that bundled scripts and CSS files return with the cache: no-cache,expires: -1 and pragma: no-cache headers. Of course, this has nothing to do with Azure. To prove this, I tested the bundles by accessing them directly from my site, instead of CDN - ie. mysite.com/bundles/mybundle?v={myassemblyversion}. The result was the same. When I disabled the CDN, and accessed the bundled file with the v query string generated by MVC, the headers were as expected: public caching, with the expiry time of one year.

I've tried to implement the IBundleTransform interface, but the context.BundleVirtualPath is read-only (even though it says gets or sets the virtual path...). I've also tried to modify the response headers at the Application_EndRequest(), but it didn't work, either. My last bet was writing IIS outbound rules, but since my bundles (used with "custom" v query string) don't return Last-Modified header, it was a futile attempt, too.

My question is: how can I use MVC bundling with Azure CDN if I want my bundled files to be cached on the client - that is, until the v query string changes?

like image 573
Riwen Avatar asked Feb 21 '16 23:02

Riwen


People also ask

How do you use bundling and minification in MVC 5?

Bundling and minification is enabled or disabled by setting the value of the debug attribute in the compilation Element in the Web. config file. In the following XML, debug is set to true so bundling and minification is disabled. To enable bundling and minification, set the debug value to "false".

What are the two types of bundles in MVC 5?

You can see ScriptBundle and StyleBundle objects we are using for bundling the js and CSS types of files. ScriptBundle: is responsible for Script files i.e javascript (JS). StyleBundle: is responsible for stylesheet files i.e CSS.

What is difference between bundling and minification?

Bundling and minification are two techniques you can use in ASP.NET to improve page load performance for your web application. Bundling combines multiple files into a single file. Minification performs a variety of different code optimizations to scripts and CSS, which results in smaller payloads.

How do I use CDN in BundleConfig?

Include(""~/Scripts/html5shiv. js"); //fallback in debug mode. As per document: "In the code above, jQuery will be requested from the CDN while in release mode and the debug version of jQuery will be fetched locally in debug mode. When using a CDN, you should have a fallback mechanism in case the CDN request fails."


1 Answers

I know I'm a little late to the game, but I did find a workaround. I'm making use of the code by Frison B Alexander here.

The issue is that once you rewrite the querystring for StyleBundles or ScriptBundles, the default caching behavior of one year resets to no-cache. This is solved by regenerating the exact same querystring per bundle that the MVC framework uses when specifying each Bundle's CDNPath.

Here's how it's done using the MVC Web App template. Here's the BundleConfig class:

public class BundleConfig
    {
        public static void RegisterBundles(BundleCollection bundles)
        {            
            //we have to go ahead and add our Bundles as if there is no CDN involved.
            //this is because the bundle has to already exist in the BundleCollection
            //in order to get the hash that the MVC framework will generate for the
            //querystring. 
            Bundle jsBundle = new ScriptBundle("~/scripts/js3").Include(
                 "~/Scripts/jquery-{version}.js",
                 "~/Scripts/modernizr-*",
                 "~/Scripts/bootstrap.js",
                 "~/Scripts/respond.js");
            bundles.Add(jsBundle);

            Bundle cssBundle = new StyleBundle("~/content/css3").Include(
                      "~/Content/bootstrap.css",
                      "~/Content/site.css");
            bundles.Add(cssBundle);

#if Debug
            bundles.UseCdn = false; 
#else
            bundles.UseCdn = true;
            //grab our base CDN hostname from web.config...
            string cdnHost = ConfigurationManager.AppSettings["CDNHostName"];

            //get the hashes that the MVC framework will use per bundle for 
            //the querystring. 
            string jsHash = GetBundleHash(bundles, "~/scripts/js3");
            string cssHash = GetBundleHash(bundles, "~/content/css3");

            //set up our querystring per bundle for the CDN path. 
            jsBundle.CdnPath = cdnHost + "/scripts/js3?v=" + jsHash;
            cssBundle.CdnPath = cdnHost + "/content/css3?v=" + cssHash;
#endif
        }

        //Frison B Alexander's code:
        private static string GetBundleHash(BundleCollection bundles, string bundlePath)
        {

            //Need the context to generate response
            var bundleContext = new BundleContext(new HttpContextWrapper(HttpContext.Current), BundleTable.Bundles, bundlePath);

            //Bundle class has the method we need to get a BundleResponse
            Bundle bundle = BundleTable.Bundles.GetBundleFor(bundlePath);
            var bundleResponse = bundle.GenerateBundleResponse(bundleContext);

            //BundleResponse has the method we need to call, but its marked as
            //internal and therefor is not available for public consumption.
            //To bypass this, reflect on it and manually invoke the method
            var bundleReflection = bundleResponse.GetType();

            var method = bundleReflection.GetMethod("GetContentHashCode", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);

            //contentHash is whats appended to your url (url?###-###...)
            var contentHash = method.Invoke(bundleResponse, null);
            return contentHash.ToString(); 
        }
    }
like image 149
Rob Reagan Avatar answered Oct 14 '22 13:10

Rob Reagan