Reputation: 40497
We have faced a problem on one of our production sites. JavaScript file was updated for a page and uploaded to IIS. We directly include file using
<script src="PATH_TO_SCRIPT" type="text/javascript"></script>
We started receiving complaints from clients that the page is broken. It was happening as the JS file was cached on client machines and was not refreshed from server.
How can we avoid this kind of scenarios without changing javascript file name in future?
ASP.Net bundling and minification might be helpful. But there are lot of pages and site is quite legacy. Almost all the pages have some heavy logic written in associated js file.
The site is running .Net 4.0 and IIS 7
Upvotes: 0
Views: 1730
Reputation: 40497
Inspired from Darin's solution I decided to go with Bundling and Minification to get the benefits of all the goodies it has to offer, I came up with the following solution. Add a static class with Extension method for Page type:
public static class ScriptExtensions
{
public static string Script(this Page page, string relativeUrl)
{
var path = page.Server.MapPath(relativeUrl);
if (File.Exists(path))
{
return BundlesConfig.AddPageScript(relativeUrl);
}
return string.Empty;
}
}
The BundlesConfig
class contians methods to generate bundle for js file and add to Bundles:
public class BundlesConfig
{
private static readonly ICollection<string> addedScripts
= new HashSet<string>();
private static readonly string bundleTemplate = "~/bundles/scripts/{0}";
internal static string AddPageScript(string relativeUrl)
{
var fileName = CleanFileName(relativeUrl);
var bundleName = string.Format(bundleTemplate, fileName);
if(!addedScripts.Contains(fileName))
{
var bundle = new ScriptBundle(bundleName);
bundle.Include(relativeUrl);
addedScripts.Add(fileName);
BundleTable.Bundles.Add(bundle);
}
return System.Web.Optimization.Scripts.Render(bundleName).ToHtmlString();
}
private static string CleanFileName(string url)
{
if (url.Contains("/"))
{
return url.Substring(url.LastIndexOf("/") + 1).Replace('.', '_')
.Replace("-", "__");
}
return url.Replace('.', '_').Replace("-", "__");
}
}
Now on pages instead of standard script
tag:
<scrip type="text/javascript" src="/scripts/jquery-min.js"></script>
we use:
<%= this.Script("~/Scripts/jquery-min.js") %>
The method spits following:
<script type="text/javascript" src="/bundles/scripts/jquery__min_js?v=...."></script>
Upvotes: 0
Reputation: 1038710
Bundling and minification is indeed the correct way to handle this because it will take care of properly appending the correct version number to the url when rendering the script.
But if this is a legacy site and for some reasons you cannot use bundling, one possibility would be to write a server side helper that will generate the script tag and calculate a checksum of the file and append the proper query string parameter:
public static class ScriptExtensions
{
public static string Script(this Page page, string relativeUrl)
{
var path = page.Server.MapPath(relativeUrl);
if (File.Exists(path))
{
return string.Format(
"<script type=\"type/javascript\" src=\"{0}?v={1}\"></script>",
page.ResolveUrl(relativeUrl),
Hash(path)
);
}
return string.Empty;
}
private static string Hash(string file)
{
using (var stream = File.OpenRead(file))
using (var bs = new BufferedStream(stream))
{
using (var sha1 = new SHA1Managed())
{
byte[] hash = sha1.ComputeHash(bs);
var result = new StringBuilder(2 * hash.Length);
foreach (byte b in hash)
{
result.AppendFormat("{0:X2}", b);
}
return result.ToString();
}
}
}
}
and then inside your WebForm use the helper to include your scripts:
<%= this.Script("~/scripts/example.js") %>
which will emit the following markup:
<script type="type/javascript" src="/scripts/example.js?v=3C222D8DFA2A02A02E9A585EA6FE0D95673E8B4A"></script>
Now when you change the contents of the script file, its SHA1 checksum will be different and a different version query string parameter will be generated and appended busting all client side cache.
Upvotes: 2