Maybe you are using the Web Essentials Visual Studio extension or you get your minified CSS and JS files from a Grunt task or similar. Here's a concept of how to serve them on version unique URLs without using querystrings or changing filenames.

NOTE 1: About using querystrings for cachebusting, there used to be a PageSpeed recommendation to remove query strings from static resources which is probably good practise to still follow.

NOTE 2: After building this I found Mads Kristensen's Cache busting in ASP.NET post which uses the same basic concepts. However I prefer using the file's hash instead of last changed date's ticks and with my example the minified file is served from the same directory as the original files which reduces the risks for problems in my experience. My post also have some pointers for use with an EPiServer Alloy MVC Site.

First I added these standard rewrite feature rules to my Web.config.

<system.webServer>
	..
	<rewrite>
		<rules>
			<rule name="CssRewrite"
				  stopProcessing="true">
				<match url="^gui/css/krompaco.min.(.*).css$" />
				<action type="Rewrite"
						url="/gui/css/krompaco.min.css" />
			</rule>
			<rule name="JsRewrite"
				  stopProcessing="true">
				<match url="^gui/js/krompaco.min.(.*).js$" />
				<action type="Rewrite"
						url="/gui/js/krompaco.min.js" />
			</rule>
		</rules>
	</rewrite>
</system.webServer>

C# samples below follows EPiServer Alloy's structure.

public class LayoutModel
{
	public string CssHash { get; set; }
	public string JsHash { get; set; }

An appropriate place to set the layout properties would be in PageViewContextFactory.

model.CssHash = GetHash("~/gui/css/krompaco.min.css");
model.JsHash = GetHash("~/gui/js/krompaco.min.js");
..
private static string GetHash(string path)
{
	string ck = "HashFor" + path;

	if (HttpRuntime.Cache[ck] == null)
	{
		string localPath = HostingEnvironment.MapPath(path);

		using (var md5 = MD5.Create())
		{
			using (var stream = File.OpenRead(localPath))
			{
				var hash = md5.ComputeHash(stream);
				string nicerNameHash = BitConverter.ToString(hash)
					.Replace("-", string.Empty).ToLower();

				// Cache with a file dependency
				HttpRuntime.Cache.Insert(ck, nicerNameHash,
					new System.Web.Caching.CacheDependency(localPath));
			}
		}
	}

	return (string)HttpRuntime.Cache[ck];
}

In the layout view I link my files. For troubleshooting I support fetching the original files using a querystring parameter.

@if (Request.QueryString["debug"] == "1") {
	<link rel="stylesheet" media="all" href="/gui/css/basic.css" />
	<link rel="stylesheet" media="all" href="/gui/css/global.css" />
	<link rel="stylesheet" media="all" href="/gui/css/print.css" />
}
else
{
	<link rel="stylesheet" media="all"
		href="/gui/css/krompaco.min.@(Model.Layout.CssHash).css" />
}

And finally before closing BODY.

@if (Request.QueryString["debug"] == "1")
{
	<script src="/gui/js/vendor/jquery.js"></script>
	<script src="/gui/js/startup.js"></script>
}
else
{
	<script src="/gui/js/krompaco.min.@(Model.Layout.JsHash).js"></script>
}

Added Tuesday 4 August 2015 09:39 — Tags for this page: Episerver, ASP.NET, MVC

Comments

  1. Valdis Iljuconoks Tuesday 4 August 2015 20:39

    Code similar to your last snippet was my trigger for FeatureSwitch library. And specifically for Web.Optimizations sublibrary :) So perfect candidate...

  2. Author Comment Thursday 6 August 2015 22:39

    Had a quick browse of FeatureSwitch some months ago. Will take a closer look!

Add Your Comment