MVC4的StyleBundle不能按正确顺序渲染捆绑包。

15

我正在尝试呈现一组CSS文件,但输出顺序错误。我已经尝试了@ MVC4 Beta Minification and Bundling:Ordering files and debugging in browser的解决方案,但没有帮助。这是捆绑包:

bundles.Add(new StyleBundle("~/stylesheet")
    .Include("~/css/main.css")
    .Include("~/css/mvc.css")
    .Include("~/js/jquery.thickbox.css")
    .Include("~/js/jquery.rating.css")
    .Include("~/css/ProductListing.css")
    .Include("~/css/dropdown/dropdown.css")
    .Include("~/css/dropdown/dropdown.vertical.css")
    .Include("~/js/fancybox/jquery.fancybox-1.3.1.css")
    .Include("~/css/scartpopup.css")
    .Include("~/css/ShoppingCart.css")
    .Include("~/css/ceebox.css")
    .Include("~/css/tooltip.css")
    .Include("~/css/recent_blog_posts.css")
    .Include("~/css/ProductDetail.css")
    .Include("~/css/jquery-ui-1.7.3.custom.css")
    .Include("~/css/filter_box.css")
    .Include("~/css/custom_page.css")
    .Include("~/css/Checkout.css")
    .Include("~/css/CheckoutButton.css")
);

这里是结果,如您所见,jquery-ui 被置于顶层。

<link href="/css/jquery-ui-1.7.3.custom.css" rel="stylesheet"/>
<link href="/css/main.css" rel="stylesheet"/>
<link href="/css/mvc.css" rel="stylesheet"/>
<link href="/js/jquery.thickbox.css" rel="stylesheet"/>
<link href="/js/jquery.rating.css" rel="stylesheet"/>
<link href="/css/ProductListing.css" rel="stylesheet"/>
<link href="/css/dropdown/dropdown.css" rel="stylesheet"/>
<link href="/css/dropdown/dropdown.vertical.css" rel="stylesheet"/>
<link href="/js/fancybox/jquery.fancybox-1.3.1.css" rel="stylesheet"/>
<link href="/css/scartpopup.css" rel="stylesheet"/>
<link href="/css/ShoppingCart.css" rel="stylesheet"/>
<link href="/css/ceebox.css" rel="stylesheet"/>
<link href="/css/tooltip.css" rel="stylesheet"/>
<link href="/css/recent_blog_posts.css" rel="stylesheet"/>
<link href="/css/ProductDetail.css" rel="stylesheet"/>
<link href="/css/filter_box.css" rel="stylesheet"/>
<link href="/css/custom_page.css" rel="stylesheet"/>
<link href="/css/Checkout.css" rel="stylesheet"/>
<link href="/css/CheckoutButton.css" rel="stylesheet"/>

我该如何确保样式表按正确的顺序呈现?


当我在多个捆绑包中引用特定的CSS或直接在_Layout.cshtml中包含它时,我曾经遇到过这种情况。 - da7rutrak
它没有在多个包中被引用。必须将它放到_layout.cshtml中,因为它在所有页面中都被使用。奇怪的是,如果我将文件重命名为其他名称,比如jqui.css,问题就会消失。 - M. Mennan Kara
我的意思是你直接在_Layout.cshtml中引用了CSS文件,而不是将捆绑包包含在文件中,因为这必须发生。 - da7rutrak
啊,不是直接引用的,只在捆绑包中,谢谢 :) - M. Mennan Kara
1个回答

22

Bundling的目的不是按照CSS文件的确切顺序进行渲染,而是遵循一种不同的逻辑。如果您需要按照定义的顺序进行渲染,则应创建自定义 IBundleOrderer 并将其设置为所需的Orderer。

public class AsDefinedBundleOrderer : IBundleOrderer
{
    public IEnumerable<FileInfo> OrderFiles(BundleContext context, IEnumerable<FileInfo> files)
    {
        return files;
    }
}

var bundle = new StyleBundle("~/stylesheet");
bundle.Orderer = new AsDefinedBundleOrderer();
bundles.Add(bundle);

那么这将不会对列表产生影响,因此Render将按照相同的顺序渲染它们。

默认排序更新

Bundling使用IBundleOrderer的概念来对Bundle中的项目进行排序。 Bundle类有一个Orderer属性,其代码如下:

public IBundleOrderer Orderer
{
  get
  {
    if (this._orderer == null)
      return (IBundleOrderer) DefaultBundleOrderer.Instance;
    else
      return this._orderer;
  }
  set
  {
    this._orderer = value;
    this.InvalidateCacheEntries();
  }
}

因此,默认的排序器实际上是一个DefaultBundleOrderer,除非您使用自定义排序器进行覆盖。 IBundleOrderer具有以下签名:
public interface IBundleOrderer
{
  IEnumerable<FileInfo> OrderFiles(BundleContext context, IEnumerable<FileInfo> files);
}

DefaultBundleOrderer 的实现是通过 BundleContext 对文件进行排序的,以下是 OrderFiles 实现中的代码片段:

  foreach (BundleFileSetOrdering ordering in (IEnumerable<BundleFileSetOrdering>) context.BundleCollection.FileSetOrderList)
    DefaultBundleOrderer.AddOrderingFiles(ordering, (IEnumerable<FileInfo>) list, fileMap, foundFiles, result);

所以不同的结果是由于这个原因。当然,这不是一种随机的排序算法 :) 规则定义在BUndleCollection类中:
public static void AddDefaultFileOrderings(IList<BundleFileSetOrdering> list)
{
  if (list == null)
    throw new ArgumentNullException("list");
  BundleFileSetOrdering bundleFileSetOrdering1 = new BundleFileSetOrdering("css");
  bundleFileSetOrdering1.Files.Add("reset.css");
  bundleFileSetOrdering1.Files.Add("normalize.css");
  list.Add(bundleFileSetOrdering1);
  BundleFileSetOrdering bundleFileSetOrdering2 = new BundleFileSetOrdering("jquery");
  bundleFileSetOrdering2.Files.Add("jquery.js");
  bundleFileSetOrdering2.Files.Add("jquery-min.js");
  bundleFileSetOrdering2.Files.Add("jquery-*");
  bundleFileSetOrdering2.Files.Add("jquery-ui*");
  bundleFileSetOrdering2.Files.Add("jquery.ui*");
  bundleFileSetOrdering2.Files.Add("jquery.unobtrusive*");
  bundleFileSetOrdering2.Files.Add("jquery.validate*");
  list.Add(bundleFileSetOrdering2);
  BundleFileSetOrdering bundleFileSetOrdering3 = new BundleFileSetOrdering("modernizr");
  bundleFileSetOrdering3.Files.Add("modernizr-*");
  list.Add(bundleFileSetOrdering3);
  BundleFileSetOrdering bundleFileSetOrdering4 = new BundleFileSetOrdering("dojo");
  bundleFileSetOrdering4.Files.Add("dojo.*");
  list.Add(bundleFileSetOrdering4);
  BundleFileSetOrdering bundleFileSetOrdering5 = new BundleFileSetOrdering("moo");
  bundleFileSetOrdering5.Files.Add("mootools-core*");
  bundleFileSetOrdering5.Files.Add("mootools-*");
  list.Add(bundleFileSetOrdering5);
  BundleFileSetOrdering bundleFileSetOrdering6 = new BundleFileSetOrdering("prototype");
  bundleFileSetOrdering6.Files.Add("prototype.js");
  bundleFileSetOrdering6.Files.Add("prototype-*");
  bundleFileSetOrdering6.Files.Add("scriptaculous-*");
  list.Add(bundleFileSetOrdering6);
  BundleFileSetOrdering bundleFileSetOrdering7 = new BundleFileSetOrdering("ext");
  bundleFileSetOrdering7.Files.Add("ext.js");
  bundleFileSetOrdering7.Files.Add("ext-*");
  list.Add(bundleFileSetOrdering7);
}

当你从Application_Start调用它时:

BundleConfig.RegisterBundles(BundleTable.Bundles);

实际上,您传递了库中定义的默认BundleCollection

因此,我们将BundleFileSetOrdering实例逐个传递到:

private static void AddOrderingFiles(BundleFileSetOrdering ordering, IEnumerable<FileInfo> files, Dictionary<string, HashSet<FileInfo>> fileMap, HashSet<FileInfo> foundFiles, List<FileInfo> result)
{
  foreach (string key in (IEnumerable<string>) ordering.Files)
  {
    if (key.EndsWith("*", StringComparison.OrdinalIgnoreCase))
    {
      string str = key.Substring(0, key.Length - 1);
      foreach (FileInfo fileInfo in files)
      {
        if (!foundFiles.Contains(fileInfo) && fileInfo.Name.StartsWith(str, StringComparison.OrdinalIgnoreCase))
        {
          result.Add(fileInfo);
          foundFiles.Add(fileInfo);
        }
      }
    }
    else if (fileMap.ContainsKey(key))
    {
      List<FileInfo> list = new List<FileInfo>((IEnumerable<FileInfo>) fileMap[key]);
      list.Sort((IComparer<FileInfo>) FileInfoComparer.Instance);
      foreach (FileInfo fileInfo in list)
      {
        if (!foundFiles.Contains(fileInfo))
        {
          result.Add(fileInfo);
          foundFiles.Add(fileInfo);
        }
      }
    }
  }
}

结论

如果我们想简化这个过程,我们可以说库更喜欢某些类型的文件,并对其他文件进行排序,如果找到多个可能性,则进行一些分类。这通常是期望的行为,但正如您所看到的,它可以很容易地被AsDefinedBundleOrderer覆盖,因此不对给定的文件集进行任何操作,因此顺序保持原始状态。


1
“捆绑”不应该按照完全相同的顺序呈现CSS文件。您在哪里找到这个信息?我正在阅读此处的教程,它让人感觉最好的方法是通过显式地按照所需顺序添加文件来确保正确的排序。 - bbak
1
为了澄清,我遇到了同样的问题,你的答案确实解决了我的问题。我只是想了解StyleBundles的排序方式以及为什么它似乎与ScriptBundles的排序方式不同。所有我的ScriptBundles都按照我定义的方式排序,而无需使用自定义的IBundleOrderer。 - bbak
感谢您扩展答案!现在这样就更有意义了。 - bbak
非常好的写作。是否可以在页面底部包含一个脚本捆绑包(以便它在所有HTML元素加载后再加载)?我已经尝试过了,但迄今为止没有成功。 - wloescher
@wloescher 没问题,只需要将bundle render Razor代码放在那里就可以了 :) - Peter Porfy
有没有办法避免为每个“Bundle”设置“Orderer”?也许可以使用“bundles.FileSetOrderList”吗? - Sinjai

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接