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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12317391/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-09 22:51:52  来源:igfitidea点击:

How to force BundleCollection to flush cached script bundles in MVC4

c#asp.netasp.net-mvc-4asp.net-optimizationbundling-and-minification

提问by Jamie Treworgy

... or how I learned to stop worrying and just write code against completely undocumented APIs from Microsoft. Is there any actual documentation of the official System.Web.Optimizationrelease? 'cuz I sure can't find any, there's no XML docs, and all the blog posts refer to the RC API which is substantially different. Anyhoo..

... 或者我是如何学会不再担心并针对 Microsoft 完全未公开的 API 编写代码的。有没有正式System.Web.Optimization发布的实际文档?因为我肯定找不到任何文档,没有 XML 文档,而且所有的博客文章都引用了 RC API,这是完全不同的。随便..

I am writing some code to automatically resolve javascript dependencies and am creating bundles on the fly from those dependencies. Everything works great, except if you edit scripts or otherwise make changes that would affect a bundle without restarting the application, the changes won't be reflected. So I added an option to disable caching of the dependencies for use in development.

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

However, apparently BundleTablescaches the URL even if the bundle collection has changed. For example, in my own code when I want to re-create a bundle I do something like this:

但是,即使捆绑集合已更改,显然也会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"

Whenever I remove & recreate a bundle with the same alias, absolutely nothing happens: the bundleUrlreturned from ResolveBundleUrlis the same as before I removed & recreated the bundle. By "the same" I mean that the content hash is unchanged to reflect the new contents of the bundle.

每当我删除并重新创建一个具有相同别名的包,绝对不会发生任何事情:bundleUrl返回ResolveBundleUrl的内容与我删除并重新创建包之前相同。“相同”是指内容哈希不变以反映包的新内容。

edit... actually, it's much worse than that. The bundle itselfis cached somehow outside of the Bundlescollection. If I just generate my own random hash to prevent the browser from caching the script, ASP.NET returns the old script. So, apparently, removing a bundle from BundleTable.Bundlesdoes not actually do anything.

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

I can simply change the alias to get around this problem, and that is OK for development, but I don't like that idea since it means either I have to deprecate aliases after each page load, or have a BundleCollection that grows in size on every page load. If you left this on in a production environment, it would be a disaster.

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

So it seems that when a script is served, it gets cached independent of the actual BundleTables.Bundlesobject. So if you re-use a URL, even if you've removed the bundle that it referred to before reusing it, it responds with whatever's in its cache, and altering the Bundlesobject does not flush the cache -- so only newitems (or rather, new items with a different name) would ever be used.

因此,似乎在提供脚本时,它会独立于实际BundleTables.Bundles对象进行缓存。因此,如果您重新使用一个 URL,即使您在重新使用它之前删除了它引用的包,它也会响应其缓存中的任何内容,并且更改Bundles对象不会刷新缓存——因此只有项目(或相反,将永远使用具有不同名称的新项目。

The behavior seems odd... removing something from the collection should remove it from the cache. But it doesn't. There must be a way to flush this cache and have it use the current contents of the BundleCollectioninstead of what it cached when that bundle was first accessed.

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

Any idea how I would do this?

知道我会怎么做吗?

There is this ResetAllmethod which has an unknown purpose but it just breaks things anyway so that isn't it.

有一种ResetAll方法具有未知的目的,但无论如何它都会破坏事物,所以不是这样。

采纳答案by Hao Kung

We hear your pain on documentation, unfortunately this feature is still changing quite fast, and generating documentation has some lag, and can be outdated almost immediately. Rick's blog postis up to date, and I've tried to answer questions here as well to spread current info in the meantime. We are currently in the process of setting up our official codeplex site which will have always current documentation.

我们听到您对文档的痛苦,不幸的是,此功能仍在快速变化,生成文档有一些滞后,并且几乎立即就会过时。 Rick 的博客文章是最新的,我也尝试在这里回答问题,同时传播当前信息。我们目前正在建立我们的官方 codeplex 站点,该站点将始终具有最新的文档。

Now in regards to your specific issue of how to flush bundles form the cache.

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

  1. We store the bundled response inside of the ASP.NET cache using a key generated off of the bundle url requested, i.e. Context.Cache["System.Web.Optimization.Bundle:~/bundles/jquery"]we also setup cache dependencies against all of the files and directories that were used to generate this bundle. So if any of the underlying files or directories change, the cache entry will get flushed.

  2. We don't really support live updating of the BundleTable/BundleCollection on a per request basis. The fully supported scenario is that bundles are configured during app start(this is so everything works properly in the web farm scenario, otherwise some bundle requests would end up being 404's if sent to the wrong server). Looking at your code example, my guess is that you are trying to modify the bundle collection dynamically on a particular request? Any kind of bundle administration/reconfiguration should be accompanied by an appdomain reset to guarantee everything has been setup correctly.

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

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

So avoid modifying your bundle definitions without recycling your app domain. You are free to modify the actual files inside of your bundles, that should automatically be detected and generate new hashcodes for your bundle urls.

因此,避免在不回收您的应用程序域的情况下修改您的包定义。您可以自由修改包中的实际文件,这些文件应该会被自动检测到并为您的包 url 生成新的哈希码。

回答by LeftyX

I've got a similar problem.
In my class BundleConfigI was trying to see what was the effect of using BundleTable.EnableOptimizations = true.

我有一个类似的问题。
在我的课堂上,BundleConfig我试图看看使用BundleTable.EnableOptimizations = true.

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

        bundles.Add(...);
    }
}

Everything was working fine.
At some point I was doing some debugging and set the property to false.
I struggled to understand what was happening cause it seemed that the bundle for jquery (the first one) wouldn't be resolved and loaded (/bundles/jquery?v=).

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

After some swearing I think(?!) I've managed to sort things out. Try to add bundles.Clear()and bundles.ResetAll()at the beginning of the registration and things should start to work again.

在骂了几句之后,我想(?!)我已经设法解决了问题。尝试在注册开始时添加bundles.Clear()bundles.ResetAll(),事情应该再次开始工作。

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

        BundleTable.EnableOptimizations = false;

        bundles.Add(...);
    }
}

I've realized I need to run these two methods only when I change the EnableOptimizationsproperty.

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

UPDATE:

更新:

Digging deeper I've found out that BundleTable.Bundles.ResolveBundleUrland @Scripts.Urlseem to have problems to resolve the bundle path.

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

For sake of simplicity I've added a few images:

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

image 1

图 1

I have turned off the optimization and bundled a few scripts.

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

image 2

图 2

The same bundle is included in the body.

正文中包含相同的捆绑包。

image 3

图 3

@Scripts.Urlgives me the "optimized" path of the bundle while @Scripts.Rendergenerates the proper one.
Same thing happens with BundleTable.Bundles.ResolveBundleUrl.

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

I am using Visual Studio 2010 + MVC 4 + Framework .Net 4.0.

我使用的是 Visual Studio 2010 + MVC 4 + Framework .Net 4.0。

回答by tulde23

Have you tried deriving from (StyleBundleor ScriptBundle), adding no inclusions in your constructor and then overriding

您是否尝试过从StyleBundleScriptBundle)派生,在构造函数中不添加任何内容,然后覆盖

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

I do this for dynamic style sheets and EnumerateFiles gets called on every request. It's probably not the greatest solution but it works.

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

回答by Zac

Bearing in mind Hao Kung's recommendations to not do this because of web farm scenarios, I think there are a lot of scenarios where you might want to do this. Here is a solution:

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

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

You can call the above code at any time and your bundles will get updated. This works both when EnableOptimizations is true or false - in other words, this will throw out the correct markup in debug or live scenarios, with:

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

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

回答by FriendScottN

I also ran into issues with updating bundles without rebuilding. Here are the important things to understand:

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

  • The bundle DOES NOT get updated if the file paths change.
  • The bundle DOES get updated if the bundle's virtual path changes.
  • The bundle DOES get updated if the files on disk change.
  • 如果文件路径更改,则捆绑包不会更新。
  • 如果包的虚拟路径更改,包会更新。
  • 如果磁盘上的文件发生变化,捆绑包会更新。

So knowing that, if you're doing dynamic bundling, you can write some code to make the bundle's virtual path be based on the file paths. I recommend hashing the file paths and appending that hash to the end of the bundle's virtual path. This way when the file paths change so does the virtual path and the bundle will update.

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

Here's the code I ended up with that solved the issue for me:

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

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

回答by SY6Dave

Apologies to revive a dead thread, however I ran into a similar issue with Bundle caching in an Umbraco site where I wanted the stylesheets/scripts to automatically minify when the user changed the pretty version in the backend.

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

The code I already had was (in the onSaved method for the stylesheet):

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

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

and (onApplicationStarted):

和(onApplicationStarted):

BundleTable.EnableOptimizations = true;

No matter what I tried, the "~/bundles/styles.min.css" file didn't seem to change. In the head of my page, I was originally loading in the stylesheet like so:

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

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

However, I got it to work by changing this to:

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

@Styles.Render("~/bundles/styles.min.css")

The Styles.Render method pulls in a query string at the end of the file name which I am guessing is the cache key described by Hao above.

Styles.Render 方法在文件名的末尾拉入一个查询字符串,我猜是上面 Hao 描述的缓存键。

For me, it was as simple as that. Hope this helps anyone else like me who was googling this for hours and could only find several year old posts!

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