15

I'm trying to render a bundle of css files, but the output is in wrong order. I've tried the solution @ MVC4 Beta Minification and Bundling: Ordering files and debugging in browser , but it didn't help. Here is the bundle:

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

And here is the result, as you can see the jquery-ui comes to top.

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

How can I make sure that the stylesheets are rendered in correct order?

Community
  • 1
  • 1
M. Mennan Kara
  • 10,072
  • 2
  • 35
  • 39
  • I've had this happen when I had the specific CSS referenced in more than one bundle or when including it directly in the _Layout.cshtml. – da7rutrak Feb 08 '13 at 15:38
  • It's not referenced in more than one bundle. Have to put it to _layout.cshtml, it's used in all pages. Weirdly, if I rename the file to something else, like jqui.css, problem goes away. – M. Mennan Kara Feb 08 '13 at 15:44
  • I meant you referenced the CSS file directly in the _Layout.cshtml, not that you included the bundle in the file as that has to happen. – da7rutrak Feb 08 '13 at 15:46
  • Ah, no, not referenced directly, only in the bundle, thanks :) – M. Mennan Kara Feb 08 '13 at 15:47

1 Answers1

22

Bundling is not supposed to render the CSS files in the exact same order, it follows a different logic. If you need to render them as defined, then you should create a custom IBundleOrderer and set it to the bundle as the required Orderer:

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

And

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

Then this will do nothing with the list so Render will render them exactly in the same order.

Update on default ordering

Bundling uses the concept of IBundleOrderer to sort the items within a Bundle. The Bundle class has it's Orderer property which looks like this:

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

So the default orderer is actually a DefaultBundleOrderer until you overwrite it with your custom orderer.

The IBundleOrderer has the following signature:

public interface IBundleOrderer
{
  IEnumerable<FileInfo> OrderFiles(BundleContext context, IEnumerable<FileInfo> files);
}

The DefaultBundleOrderer implementation of this orders the files by the BundleContext, here is a snippet from the implementation of OrderFiles:

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

So the different result happens because this. This is of course not a random sort algorithm :) The rules are defined in the BUndleCollection class:

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

So when you call this from Application_Start:

BundleConfig.RegisterBundles(BundleTable.Bundles);

Actually you pass the default BundleCollection defined in the library.

So we have the BundleFileSetOrdering instances passed one-by-one into:

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

Conclusion

If we want to simplify the process we can say that the library prefers some kind of files and makes some sorting on the other files if multiple possibilities found. This is the expected behavior most of the time but as you can see it is easly overridable with the AsDefinedBundleOrderer so it does nothing with the given file set so the order remains the original.

Peter Porfy
  • 8,921
  • 3
  • 31
  • 41
  • 1
    "Bundling is not supposed to render the CSS files in the exact same order". Where did you find this information? I'm reading the tutorial [here](http://www.asp.net/mvc/tutorials/mvc-4/bundling-and-minification) and it makes it sound like the preferred approach to make sure you get the correct ordering is by explicitly adding the files in the order you want them. – bbak Apr 11 '13 at 15:49
  • 1
    And to clarify, I was having the same issue and your answer did solve it for me. I just want to understand how the ordering for StyleBundles works and why it appears to function differently than the ordering for ScriptBundles. All my ScriptBundles order the files the way I define without needing to use the custom IBundleOrderer – bbak Apr 11 '13 at 16:23
  • Thanks for expanding the answer! That makes a lot more sense now. – bbak Apr 12 '13 at 16:12
  • Excellent write up. Is it possible to include a script bundle at the BOTTOM of the page (so it loads after all of the HTML elements)? I've tried it, but no success so far. – wloescher Dec 31 '14 at 15:03
  • @wloescher sure, just put the bundle render razor code there :) – Peter Porfy Jan 30 '15 at 21:20
  • Is there a way to avoid setting the `Orderer` for every `Bundle`? Maybe with `bundles.FileSetOrderList`? – Sinjai Feb 12 '18 at 10:57