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


85

...或者我是如何学会不再担心的,只是针对Microsoft完全未记录的API编写代码。官方System.Web.Optimization发布有任何实际文档吗?“因为我肯定找不到任何东西,没有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,即使您在重用URL之前已经删除了它所引用的包,它也会以缓存中的内容作为响应,并且更改Bundles对象不会刷新缓存-因此,只有项目(或而是会使用其他名称的新商品)。

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

知道我该怎么做吗?

有一种ResetAll方法的目的不明,但它还是会破坏事物,不是吗。


这里同样的问题。我想我已经解决了我的问题。尝试看看是否适合您。完全同意。System.Web.Optimization的文档很垃圾,您可以在Internet上找到的所有示例均已过时。
LeftyX 2012年

2
+1是最重要的参考,同时还对MS的信任期望发表了一些尖刻的评论。同时也提出我想要答案的问题。
2013年

Answers:


33

我们听到您在文档方面的痛苦,很遗憾,此功能仍在快速更改,并且生成文档存在一些滞后,并且几乎可以立即过时。 里克(Rick)的博客文章是最新的,我也尝试在此处回答问题,以同时传播当前信息。我们目前正在建立我们的官方Codeplex网站,该网站将始终提供最新文档。

现在,关于如何清除捆绑形成缓存的特定问题。

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

  2. 我们真的不支持根据每个请求实时更新BundleTable / BundleCollection。完全受支持的方案是在应用程序启动期间配置了捆绑包(这样,所有事情都可以在Web服务器场方案中正常运行,否则,如果将某些捆绑包请求发送到错误的服务器,最终将是404的)。查看您的代码示例,我猜您是在尝试根据特定请求动态修改bundle集合吗?任何种类的捆绑软件管理/重新配置都应伴随appdomain重置,以确保一切都已正确设置。

因此,避免在不回收应用程序域的情况下修改捆绑软件定义。您可以随意修改包中的实际文件,这些文件将被自动检测并为包URL生成新的哈希码。


2
感谢您将您的直接知识带到这里!是的-我正在尝试动态修改包集合。捆绑软件是根据另一个脚本中描述的一组依赖关系(即本身,不一定是捆绑软件的一部分)构建的-这就是为什么我遇到这个问题的原因。由于更改捆绑软件中的脚本会强制执行刷新,因此可以这样做-是否可以添加手动刷新方法?这不是至关重要的-这是为了在开发过程中提供方便-但是我讨厌创建代码,如果不小心在产品上使用它们可能会导致问题。
Jamie Treworgy 2012年

您还可以详细说明网络农场问题吗?在应用程序启动后添加捆绑包会导致它仅在创建它的服务器上可用-还是只是尝试更改现有捆绑包?对于我正在尝试执行的操作,这可能会有点麻烦,因为它需要对依赖项进行运行时解析。
Jamie Treworgy 2012年

当然,我们可以添加一个显式的缓存刷新等效方法,它已经在内部存在。关于Web场问题,基本上假设您有两个Web服务器A和B,您的请求将发送到A,该A添加捆绑包,然后发送响应,您的客户端现在将获取捆绑包的内容,但是哎呀,请求转到没有注册捆绑软件的服务器B,其中有您的404。–
Hao Kung

1
缓存更新是惰性的,第一次使用捆绑软件时(通常是通过渲染对捆绑软件的引用),将其添加到缓存中。如果您有一个等效的应用程序启动钩,那么在开始处理请求之前,您可以在所有Web服务器上设置捆绑软件,那就可以了。
郝公

2
据我所知这是行不通的。也就是说,如果我更改组成文件,则不会按此处所述清除服务器缓存。您必须回收该东西才能进行任何更改。有人知道官方文件实际上在哪里吗?
phil13年

21

我有一个类似的问题。
在课堂上,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似乎有问题要解决的束路径。

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

图片1

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

图片2

主体中包含相同的捆绑包。

图片3

@Scripts.Url给我“捆绑”的“优化”路径,同时@Scripts.Render生成正确的捆绑。
发生同样的事情BundleTable.Bundles.ResolveBundleUrl

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


嗯...问题是我实际上并不想清除bundle表,因为它将包含来自不同页面(由不同的依赖集创建)的许多其他表。但是,由于这实际上只是为了在开发环境中工作,因此我认为我可以复制它的内容,然后清除它,然后再次添加它们,如果这样会刷新缓存。效率低下,但如果可行,对于开发人员来说已经足够了。
Jamie Treworgy 2012年

同意,但这是我唯一的选择。我整个下午都在努力了解问题所在。
LeftyX 2012年

2
我只是试过了,还是不刷新缓存!我清除了,ResetAll并尝试EnableOptimizations在需要重置缓存时在启动和内联时都设置为false,但没有任何反应。啊
Jamie Treworgy 2012年

如果开发人员可以发布一篇简短的博客文章,甚至只写一篇关于这些对象中方法的文章,那肯定会很好:)
Jamie Treworgy 2012年

6
因此,仅说明这些方法的作用:Scripts.Url只是BundleTable.Bundles.ResolveBundleUrl的别名,它还会解析非捆绑软件的URL,因此它是一个通用的URL解析器,恰好了解捆绑软件。Scripts.Render使用EnableOptimizations标志确定是呈现对包的引用还是构成包的组件。
郝公

8

考虑到Hao Kung建议不要因为Web场场景而执行此操作,我认为在很多场景中您可能想要执行此操作。这是一个解决方案:

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

延伸阅读这里其中谈到一点关于缓存和GenerateBundleResponse
扎克

4

我还遇到了不重建而更新包的问题。以下是需要了解的重要事项:

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

因此,知道了这一点,如果您要进行动态捆绑,则可以编写一些代码以使捆绑包的虚拟路径基于文件路径。我建议对文件路径进行哈希处理,并将该哈希值附加到捆绑软件虚拟路径的末尾。这样,当文件路径更改时,虚拟路径也将更改,并且捆绑包将更新。

这是我最终为我解决问题的代码:

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

我建议通常避免Aggregate进行字符串连接,因为这样可能会导致某些人没有反复考虑使用Painter算法固有的Schlemiel的风险+。相反,只要做string.Join("", filePaths)。即使输入非常大,也不会出现问题。
ErikE '16

3

您是否尝试过从StyleBundleScriptBundle)派生,没有在构造函数中添加任何包含,然后覆盖

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

我为动态样式表执行此操作,每个请求都调用EnumerateFiles。这可能不是最大的解决方案,但它可以工作。


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方法在文件名的末尾插入一个查询字符串,我猜这是上述郝描述的缓存键。

对我来说,就是这么简单。希望这对像我这样一直在谷歌上搜索了几个小时并且只能找到数年历史的帖子的人有所帮助!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.