MVC4 StyleBundle:你能在Debug模式下添加缓存破坏查询字符串吗?

时间:2013-02-21 15:03:27

标签: caching asp.net-mvc-4 bundle

我有一个MVC应用程序,我正在使用StyleBundle类来渲染这样的CSS文件:

bundles.Add(new StyleBundle("~/bundles/css").Include("~/Content/*.css"));

我遇到的问题是,在Debug模式下,CSS网址是单独呈现的,我有一个网络代理,可以积极地缓存这些网址。在Release模式下,我知道在最终URL中添加了一个查询字符串,以使每个版本的任何缓存无效。

是否可以将StyleBundle配置为在Debug模式下添加随机查询字符串以生成以下输出以解决缓存问题?

<link href="/stylesheet.css?random=some_random_string" rel="stylesheet"/>

6 个答案:

答案 0 :(得分:45)

您可以创建自定义IBundleTransform类来执行此操作。这是一个使用文件内容的散列附加v = [filehash]参数的示例。

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);
            }                
        }
    }
}

然后,您可以通过将类添加到捆绑包的Transforms集合来注册该类。

new StyleBundle("...").Transforms.Add(new FileHashVersionBundleTransform());

现在版本号只会在文件内容发生变化时发生变化。

答案 1 :(得分:38)

你只需要一个独特的字符串。它不一定是哈希。我们使用文件的LastModified日期并从那里获取Ticks。正如@Todd所说,打开和阅读文件很昂贵。刻度足以输出在文件更改时更改的唯一编号。

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);
            }
        }
    }
}

以及如何使用它:

bundles.Add(new StyleBundle("~/bundles/css")
    .Include("~/Content/*.css")
    .WithLastModifiedToken());

这就是MVC所写的:

<link href="bundles/css/site.css?v=635983900813469054" rel="stylesheet"/>

也适用于Script bundle。

答案 2 :(得分:13)

此库可以在调试模式下将缓存清除哈希添加到您的包文件中,以及其他一些缓存破坏的内容:https://github.com/kemmis/System.Web.Optimization.HashCache

您可以将HashCache应用于BundlesCollection

中的所有捆绑包

在BundlesCollection实例上执行ApplyHashCache()扩展方法 将所有捆绑包添加到集合后。

BundleTable.Bundles.ApplyHashCache();

或者您可以将HashCache应用于单个Bundle

创建HashCacheTransform的实例并将其添加到所需的包实例中 将HashCache应用于。

var myBundle = new ScriptBundle("~/bundle_virtual_path").Include("~/scripts/jsfile.js");
myBundle.Transforms.Add(new HashCacheTransform());

答案 3 :(得分:8)

我遇到了同样的问题,但升级后在客户端浏览器中使用了缓存版本。我的解决方案是在我自己的渲染器中将调用包装到@Styles.Render("~/Content/css"),将我们的版本号附加到查询字符串中,如下所示:

    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);
    }

然后在视图中我喜欢这个:

@RenderHelpers.RenderCacheSafe("~/Content/css")

答案 4 :(得分:2)

目前还没有,但很快就会添加(现在计划在1.1稳定版中发布,您可以在此处跟踪此问题:Codeplex

答案 5 :(得分:1)

请注意,这是为脚本编写的,但也适用于样式(只需更改这些关键字)

以@ Johan的答案为基础:

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);
}

用法:

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

更换

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

优点:

  • 适用于System.Web.Optimizations的v1.0.0.0
  • 适用于捆绑包中的多个文件
  • 获取每个文件的文件修改日期而不是散列,而不是组

此外,当您需要快速解决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);
}

用法:

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

更换:

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

(也取代了许多其他替代查找)