MVC4 StyleBundle: Can you add a cache-busting query string in Debug mode?

后端 未结 6 1524
不思量自难忘°
不思量自难忘° 2020-12-13 06:16

I\'ve got an MVC application and I\'m using the StyleBundle class for rendering out CSS files like this:

bundles.Add(new StyleBundle(\"~/bundles         


        
相关标签:
6条回答
  • 2020-12-13 06:29

    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

    You can apply HashCache to all bundles in a BundlesCollection

    Execute the ApplyHashCache() extension method on the BundlesCollection Instance after all bundles have been added to the collection.

    BundleTable.Bundles.ApplyHashCache();
    

    Or you can apply HashCache to a single Bundle

    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());
    
    0 讨论(0)
  • 2020-12-13 06:29

    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")
    
    0 讨论(0)
  • 2020-12-13 06:31

    Not currently but this is slated to be added soon (right now scheduled for the 1.1 stable release, you can track this issue here: Codeplex

    0 讨论(0)
  • 2020-12-13 06:41

    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.

    0 讨论(0)
  • 2020-12-13 06:45

    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.

    0 讨论(0)
  • 2020-12-13 06:45

    Note this is written for Scripts but also works for Styles (just change those key words)

    Building on @Johan's answer:

    public static IHtmlString RenderBundle(this HtmlHelper htmlHelper, string path)
    {
        var context = new BundleContext(htmlHelper.ViewContext.HttpContext, BundleTable.Bundles, string.Empty);
        var bundle = System.Web.Optimization.BundleTable.Bundles.GetBundleFor(path);
        var html = System.Web.Optimization.Scripts.Render(path).ToString();
        foreach (var item in bundle.EnumerateFiles(context))
        {
            if (!html.Contains(item.Name))
                continue;
    
            html = html.Replace(item.Name, item.Name + "?" + item.LastWriteTimeUtc.ToString("yyyyMMddHHmmss"));
        }
    
        return new HtmlString(html);
    }
    
    public static IHtmlString RenderStylesBundle(this HtmlHelper htmlHelper, string path)
    {
        var context = new BundleContext(htmlHelper.ViewContext.HttpContext, BundleTable.Bundles, string.Empty);
        var bundle = System.Web.Optimization.BundleTable.Bundles.GetBundleFor(path);
        var html = System.Web.Optimization.Styles.Render(path).ToString();
        foreach (var item in bundle.EnumerateFiles(context))
        {
            if (!html.Contains(item.Name))
                continue;
    
            html = html.Replace(item.Name, item.Name + "?" + item.LastWriteTimeUtc.ToString("yyyyMMddHHmmss"));
        }
    
        return new HtmlString(html);
    }
    

    Usage:

    @Html.RenderBundle("...")
    @Html.RenderStylesBundle("...")
    

    Replacing

    @Scripts.Render("...")
    @Styles.Render("...")
    

    Benefits:

    • Works for v1.0.0.0 of System.Web.Optimizations
    • Works on multiple files in the bundle
    • Gets the file modification date, rather than hashing, of each file, rather than a group

    Also, when you need to quickly workaround Bundler:

    public static MvcHtmlString ResolveUrl(this HtmlHelper htmlHelper, string url)
    {
        var urlHelper = new UrlHelper(htmlHelper.ViewContext.RequestContext);
        var resolvedUrl = urlHelper.Content(url);
    
        if (resolvedUrl.ToLower().EndsWith(".js") || resolvedUrl.ToLower().EndsWith(".css"))
        {
            var localPath = HostingEnvironment.MapPath(resolvedUrl);
            var fileInfo = new FileInfo(localPath);
            resolvedUrl += "?" + fileInfo.LastWriteTimeUtc.ToString("yyyyMMddHHmmss");
        }
    
        return MvcHtmlString.Create(resolvedUrl);
    }
    

    Usage:

    <script type="text/javascript" src="@Html.ResolveUrl("~/Scripts/jquery-1.9.1.min.js")"></script>
    

    Replacing:

    <script type="text/javascript" src="@Url.Content("~/Scripts/jquery-1.9.1.min.js")"></script>
    

    (Also replaces many other alternative lookups)

    0 讨论(0)
提交回复
热议问题