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?
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".
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.
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.
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."
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();
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With