56

我有一个 MVC 应用程序,我正在使用StyleBundle该类来渲染 CSS 文件,如下所示:

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

我遇到的问题是,在Debug模式下,CSS url 是单独呈现的,并且我有一个 Web 代理可以主动缓存这些 url。在Release模式下,我知道一个查询字符串被添加到最终 url 以使每个版本的任何缓存无效。

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

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

6 回答 6

55

你只需要一个唯一的字符串。它不一定是哈希。我们使用文件的 LastModified 日期并从那里获取 Ticks。正如@Todd 指出的那样,打开和读取文件的成本很高。Ticks 足以输出一个在文件更改时更改的唯一编号。

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"/>

也适用于脚本包。

于 2016-05-09T16:02:45.307 回答
48

您可以创建一个自定义 IBundleTransform 类来执行此操作。这是一个示例,它将使用文件内容的哈希附加 av=[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());

现在版本号只有在文件内容改变时才会改变。

于 2014-10-21T15:32:24.480 回答
16

这个库可以在调试模式下将缓存清除哈希添加到您的捆绑文件中,以及其他一些缓存清除的东西: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());
于 2015-01-11T12:19:10.440 回答
9

我遇到了同样的问题,但升级后客户端浏览器中有缓存版本。我的解决方案是将调用包装@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")
于 2014-03-14T08:27:06.747 回答
2

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

于 2013-02-22T11:05:51.153 回答
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>

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

于 2016-03-19T13:20:36.230 回答