Search code examples
c#asp.netasp.net-mvcbundling-and-minificationweb-optimization

Get access to the URL's being used in System.Web.Optimization


Background: I'm using the HTML 5 Offline App Cache and dynamically building the manifest file. Basically, the manifest file needs to list each of the static files that your page will request. Works great when the files are actually static, but I'm using Bundling and Minification in System.Web.Optimization, so my files are not static.

When in the DEBUG symbol is loaded (i.e. debugging in VS) then the actual physical files are called from the MVC View. However, when in Release mode, it calls a virtual file that could look something like this: /bundles/scripts/jquery?v=FVs3ACwOLIVInrAl5sdzR2jrCDmVOWFbZMY6g6Q0ulE1

So my question: How can I get that URL in the code to add it to the offline app manifest?

I've tried:

        var paths = new List<string>()
        {
            "~/bundles/styles/common",
            "~/bundles/styles/common1024",
            "~/bundles/styles/common768",
            "~/bundles/styles/common480",
            "~/bundles/styles/frontend",
            "~/bundles/scripts/jquery",
            "~/bundles/scripts/common",
            "~/bundles/scripts/frontend"
        };

        var bundleTable = BundleTable.Bundles;
        foreach (var bundle in bundleTable.Where(b => paths.Contains(b.Path)))
        {
            var bundleContext = new BundleContext(this.HttpContext, bundleTable, bundle.Path);
            IEnumerable<BundleFile> files = bundle.GenerateBundleResponse(bundleContext).Files;
            foreach (var file in files)
            {
                var filePath = file.IncludedVirtualPath.TrimStart(new[] { '~' });
                sb.AppendFormat(formatFullDomain, filePath);
            }
        } 

As well as replacing GenerateBundleResponse() with EnumerateFiles(), but it just always returns the original file paths.

I'm open to alternative implementation suggestions as well. Thanks.

UPDATE: (7/7/14 13:45)

As well as the answer below I also added this Bundles Registry class to keep a list of the required static files so that it works in debug mode in all browsers. (See comments below)

    public class Registry
    {
        public bool Debug = false;
        public Registry()
        {
            SetDebug();
        }
        [Conditional("DEBUG")]
        private void SetDebug()
        {
            Debug = true;
        }

        public IEnumerable<string> CommonScripts
        {
            get
            {
                if (Debug)
                {
                    return new string[]{
                        "/scripts/common/jquery.validate.js",
                        "/scripts/common/jquery.validate.unobtrusive.js",
                        "/scripts/common/knockout-3.1.0.debug.js",
                        "/scripts/common/jquery.timepicker.js",
                        "/scripts/common/datepicker.js",
                        "/scripts/common/utils.js",
                        "/scripts/common/jquery.minicolors.js",
                        "/scripts/common/chosen.jquery.custom.js"
                    };
                }
                else
                {
                    return new string[]{
                        "/scripts/common/commonbundle.js"
                    };
                }
            }
        }
    }

I'm by no means happy with this solution. Please make suggestions if you can improve on this.


Solution

  • I can suggest an alternative from this blog post create your own token.

    In summary the author suggests using web essentials to create the bundled file and then creating a razor helper to generate the token, in this case based on the last changed date and time.

    public static class StaticFile
    {
        public static string Version(string rootRelativePath)
        {
            if (HttpRuntime.Cache[rootRelativePath] == null)
            {
                var absolutePath = HostingEnvironment.MapPath(rootRelativePath);
                var lastChangedDateTime = File.GetLastWriteTime(absolutePath);
    
                if (rootRelativePath.StartsWith("~"))
                {
                    rootRelativePath = rootRelativePath.Substring(1);
                }
    
                var versionedUrl = rootRelativePath + "?v=" + lastChangedDateTime.Ticks;
    
                HttpRuntime.Cache.Insert(rootRelativePath, versionedUrl, new CacheDependency(absolutePath));
            }
    
            return HttpRuntime.Cache[rootRelativePath] as string;
        }
    }
    

    Then you can reference the bundled file like so...

    @section scripts {
    <script src="@StaticFile.Version("~/Scripts/app/myAppBundle.min.js")"></script>}
    

    Then you have control of the token and can do what you want with it.