You just need a unique string. It doesn't have to be Hash. We use the LastModified date of the file and get the Ticks from there. Opening and reading the file is expensive as @Todd noted. Ticks is enough to output a unique number that changes when the file is changed.
internal static class BundleExtensions
{
public static Bundle WithLastModifiedToken(this Bundle sb)
{
sb.Transforms.Add(new LastModifiedBundleTransform());
return sb;
}
public class LastModifiedBundleTransform : IBundleTransform
{
public void Process(BundleContext context, BundleResponse response)
{
foreach (var file in response.Files)
{
var lastWrite = File.GetLastWriteTime(HostingEnvironment.MapPath(file.IncludedVirtualPath)).Ticks.ToString();
file.IncludedVirtualPath = string.Concat(file.IncludedVirtualPath, "?v=", lastWrite);
}
}
}
}
and how to use it:
bundles.Add(new StyleBundle("~/bundles/css")
.Include("~/Content/*.css")
.WithLastModifiedToken());
and this is what MVC writes:
<link href="bundles/css/site.css?v=635983900813469054" rel="stylesheet"/>
works fine with Script bundles too.
You can create a custom IBundleTransform class to do this. Here's an example that will append a v=[filehash] parameter using a hash of the file contents.
public class FileHashVersionBundleTransform: IBundleTransform
{
public void Process(BundleContext context, BundleResponse response)
{
foreach(var file in response.Files)
{
using(FileStream fs = File.OpenRead(HostingEnvironment.MapPath(file.IncludedVirtualPath)))
{
//get hash of file contents
byte[] fileHash = new SHA256Managed().ComputeHash(fs);
//encode file hash as a query string param
string version = HttpServerUtility.UrlTokenEncode(fileHash);
file.IncludedVirtualPath = string.Concat(file.IncludedVirtualPath, "?v=", version);
}
}
}
}
You can then register the class by adding it to the Transforms collection of your bundles.
new StyleBundle("...").Transforms.Add(new FileHashVersionBundleTransform());
Now the version number will only change if the file contents change.
This library can add the cache-busting hash to your bundle files in debug mode, as well as a few other cache-busting things: https://github.com/kemmis/System.Web.Optimization.HashCache
Execute the ApplyHashCache() extension method on the BundlesCollection Instance after all bundles have been added to the collection.
BundleTable.Bundles.ApplyHashCache();
Create an instance of the HashCacheTransform and add it to the bundle instance you want to apply HashCache to.
var myBundle = new ScriptBundle("~/bundle_virtual_path").Include("~/scripts/jsfile.js");
myBundle.Transforms.Add(new HashCacheTransform());
I've had the same problem but with cached versions in client browsers after an upgrade. My solution is to wrap the call to @Styles.Render("~/Content/css")
in my own renderer that appends our version number in the query string like this:
public static IHtmlString RenderCacheSafe(string path)
{
var html = Styles.Render(path);
var version = VersionHelper.GetVersion();
var stringContent = html.ToString();
// The version should be inserted just before the closing quotation mark of the href attribute.
var versionedHtml = stringContent.Replace("\" rel=", string.Format("?v={0}\" rel=", version));
return new HtmlString(versionedHtml);
}
And then in the view I do like this:
@RenderHelpers.RenderCacheSafe("~/Content/css")
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