如何强制BundleCollection刷新MVC4中的缓存脚本包

时间:2012-09-07 11:38:21

标签: c# asp.net asp.net-mvc-4 asp.net-optimization bundling-and-minification

...或我是如何学会停止担心的,只是针对来自Microsoft 的完全未记录的API编写代码。是否有官方System.Web.Optimization版本的实际文档? 'cuz我肯定找不到任何,没有XML文档,所有的博客文章都引用了RC API,这是完全不同的。安美居..

我正在编写一些代码来自动解析javascript依赖项,并从这些依赖项中动态创建bundle。一切都很好,除非您编辑脚本或以其他方式进行更改,这些更改会影响捆绑而不重新启动应用程序,否则不会反映更改。所以我添加了一个选项来禁用依赖项的缓存,以便在开发中使用。

但是,即使捆绑集合已更改,显然BundleTables也会缓存URL 。例如,在我自己的代码中,当我想重新创建一个包时,我会做这样的事情:

// remove an existing bundle
BundleTable.Bundles.Remove(BundleTable.Bundles.GetBundleFor(bundleAlias));

// recreate it.
var bundle = new ScriptBundle(bundleAlias);

// dependencies is a collection of objects representing scripts, 
// this creates a new bundle from that list. 

foreach (var item in dependencies)
{
    bundle.Include(item.Path);
}

// add the new bundle to the collection

BundleTable.Bundles.Add(bundle);

// bundleAlias is the same alias used previously to create the bundle,
// like "~/mybundle1" 

var bundleUrl = BundleTable.Bundles.ResolveBundleUrl(bundleAlias);

// returns something like "/mybundle1?v=hzBkDmqVAC8R_Nme4OYZ5qoq5fLBIhAGguKa28lYLfQ1"

每当我删除&使用相同的别名重新创建一个包,绝对没有任何反应:从bundleUrl返回的ResolveBundleUrl与我删除&之前相同重新创建了捆绑。 “相同”是指内容哈希值不变以反映捆绑包的新内容。

编辑 ......实际上,它比那更糟糕。 包本身以某种方式缓存在Bundles集合之外。如果我只生成自己的随机哈希以防止浏览器缓存脚本,ASP.NET将返回旧脚本。因此,显然,从BundleTable.Bundles删除捆绑实际上并没有做任何事情。

我可以简单地更改别名来解决这个问题,这对开发来说没问题,但我不喜欢这个想法,因为这意味着我必须在每次加载页面后弃用别名,或者让BundleCollection增长在每个页面加载大小。如果你把它放在生产环境中,那将是一场灾难。

因此,当提供脚本时,它会被独立于实际的BundleTables.Bundles对象进行缓存。因此,如果您重新使用URL,即使您在重用它之前删除了它所引用的包,它也会响应其缓存中的任何内容,并且更改Bundles对象不会刷新缓存 - 所以只会使用 new 项目(或更确切地说,具有不同名称的新项目)。

行为似乎很奇怪......从集合中删除某些内容应将其从缓存中删除。但事实并非如此。必须有一种方法来刷新此缓存并使其使用BundleCollection的当前内容,而不是首次访问该包时缓存的内容。

知道我会怎么做吗?

有这个ResetAll方法有一个未知的目的,但它只是打破了事情,所以不是它。

6 个答案:

答案 0 :(得分:33)

我们听到您对文档的痛苦,不幸的是,此功能仍在快速变化,生成文档有一些滞后,并且几乎可以立即过时。 Rick's blog post是最新的,我也尝试在此回答问题,同时传播当前信息。我们目前正在设置官方的codeplex网站,该网站将始终提供最新文档。

现在关于如何从缓存中清除包的具体问题。

  1. 我们使用从请求的bundle url生成的密钥将捆绑的响应存储在ASP.NET缓存中,即Context.Cache["System.Web.Optimization.Bundle:~/bundles/jquery"]我们还针对所使用的所有文件和目录设置缓存依赖性生成这个包。因此,如果任何底层文件或目录发生更改,缓存条目将被刷新。

  2. 我们并不真正支持基于每个请求实时更新BundleTable / BundleCollection。完全支持的方案是在应用程序启动期间配置捆绑包(这样在Web场方案中一切正常,否则如果发送到错误的服务器,某些捆绑请求将最终为404)。看看你的代码示例,我的猜测是你试图在特定请求上动态修改bundle集合?任何类型的捆绑管理/重新配置都应该伴随appdomain重置,以确保所有设置都正确。

  3. 因此,请勿在不回收应用域的情况下修改捆绑定义。您可以自由修改捆绑包内的实际文件,这些文件应该被自动检测并为您的捆绑网址生成新的哈希码。

答案 1 :(得分:20)

我遇到了类似的问题 在我的班级BundleConfig中,我试图了解使用BundleTable.EnableOptimizations = true的效果。

public class BundleConfig
{
    public static void RegisterBundles(BundleCollection bundles)
    {
        BundleTable.EnableOptimizations = true;

        bundles.Add(...);
    }
}

一切都很好 在某些时候,我正在做一些调试并将属性设置为false 我很难理解发生了什么,因为似乎jquery(第一个)的包不会被解析和加载(/bundles/jquery?v=)。

经过一番咒骂后,我想(?!)我已经成功解决了问题。 尝试在注册开始时添加bundles.Clear()bundles.ResetAll(),事情应该重新开始。

public class BundleConfig
{
    public static void RegisterBundles(BundleCollection bundles)
    {
        bundles.Clear();
        bundles.ResetAll();

        BundleTable.EnableOptimizations = false;

        bundles.Add(...);
    }
}

我已经意识到只有在我更改EnableOptimizations属性时才需要运行这两种方法。

<强>更新

深入挖掘我发现BundleTable.Bundles.ResolveBundleUrl@Scripts.Url似乎在解决捆绑路径方面存在问题。

为了简单起见,我添加了一些图片:

image 1

我关闭了优化并捆绑了一些脚本。

image 2

身体中包含相同的束。

image 3

@Scripts.Url为我提供了捆绑的“优化”路径,而@Scripts.Render生成了正确的路径。
BundleTable.Bundles.ResolveBundleUrl也发生了同样的事情。

我正在使用Visual Studio 2010 + MVC 4 + Framework .Net 4.0。

答案 2 :(得分:8)

考虑到郝公的建议不要因为网络农场场景而这样做,我认为有很多场景你可能想要这样做。这是一个解决方案:

BundleTable.Bundles.ResetAll(); //or something more specific if neccesary
var bundle = new Bundle("~/bundles/your-bundle-virtual-path");
//add your includes here or load them in from a config file

//this is where the magic happens
var context = new BundleContext(new HttpContextWrapper(HttpContext.Current), BundleTable.Bundles, bundle.Path);
bundle.UpdateCache(context, bundle.GenerateBundleResponse(context));

BundleTable.Bundles.Add(bundle);

您可以随时调用上述代码,您的捆绑包将会更新。这在EnableOptimizations为true或false时都有效 - 换句话说,这将在调试或实时场景中抛出正确的标记,其中包括:

@Scripts.Render("~/bundles/your-bundle-virtual-path")

答案 3 :(得分:4)

我还遇到了更新捆绑包但没有重建的问题。以下是需要了解的重要事项:

  • 如果文件路径发生变化,则捆绑包不会更新。
  • 如果捆绑包的虚拟路径发生变化,捆绑包会更新。
  • 如果磁盘上的文件发生变化,则捆绑包会更新。

所以要知道,如果你正在进行动态捆绑,你可以编写一些代码来使bundle的虚拟路径基于文件路径。我建议散列文件路径并将该散列附加到捆绑软件虚拟路径的末尾。这样,当文件路径发生变化时,虚拟路径和捆绑包也会更新。

以下代码我最终解决了这个问题:

    public static IHtmlString RenderStyleBundle(string bundlePath, string[] filePaths)
    {
        // Add a hash of the files onto the path to ensure that the filepaths have not changed.
        bundlePath = string.Format("{0}{1}", bundlePath, GetBundleHashForFiles(filePaths));

        var bundleIsRegistered = BundleTable
            .Bundles
            .GetRegisteredBundles()
            .Where(bundle => bundle.Path == bundlePath)
            .Any();

        if(!bundleIsRegistered)
        {
            var bundle = new StyleBundle(bundlePath);
            bundle.Include(filePaths);
            BundleTable.Bundles.Add(bundle);
        }

        return Styles.Render(bundlePath);
    }

    static string GetBundleHashForFiles(IEnumerable<string> filePaths)
    {
        // Create a unique hash for this set of files
        var aggregatedPaths = filePaths.Aggregate((pathString, next) => pathString + next);
        var Md5 = MD5.Create();
        var encodedPaths = Encoding.UTF8.GetBytes(aggregatedPaths);
        var hash = Md5.ComputeHash(encodedPaths);
        var bundlePath = hash.Aggregate(string.Empty, (hashString, next) => string.Format("{0}{1:x2}", hashString, next));
        return bundlePath;
    }

答案 4 :(得分:3)

您是否尝试从( StyleBundle ScriptBundle )派生,在构造函数中不添加任何内容,然后重写

public override IEnumerable<System.IO.FileInfo> EnumerateFiles(BundleContext context)

我为动态样式表执行此操作,并在每个请求上调用EnumerateFiles。它可能不是最好的解决方案,但它有效。

答案 5 :(得分:0)

为复活死线程而道歉,但是我在Umbraco网站遇到类似的Bundle缓存问题,我希望样式表/脚本在用户更改后端的漂亮版本时自动缩小。

我已经拥有的代码(在样式表的onSaved方法中):

 BundleTable.Bundles.Add(new StyleBundle("~/bundles/styles.min.css").Include(
                           "~/css/main.css"
                        ));

和(onApplicationStarted):

BundleTable.EnableOptimizations = true;

无论我尝试什么,“〜/ bundles / styles.min.css”文件似乎都没有改变。在我的页面的头部,我最初在样式表中加载如下:

<link rel="stylesheet" href="~/bundles/styles.min.css" />

但是,我通过将其更改为:

来实现它
@Styles.Render("~/bundles/styles.min.css")

Styles.Render方法在文件名末尾提取一个查询字符串,我猜测它是上面郝所描述的缓存键。

对我来说,就这么简单。希望这可以帮助像我这样的人在谷歌上搜索几个小时并且只能找到几年的帖子!