Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVC4 StyleBundle doesn't render the bundle in the correct order

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?

like image 910
M. Mennan Kara Avatar asked Feb 08 '13 15:02

M. Mennan Kara


People also ask

How do you override the bundling setting of Web config?

Controlling Bundling and Minification To enable bundling and minification, set the debug value to "false". You can override the Web. config setting with the EnableOptimizations property on the BundleTable class.

How to implement bundling and minification in MVC 5?

Bundling and minification can be enabled or disabled in two ways: either setting the value of the debug attribute in the compilation Element in the Web. config file or setting the enableOptimizations property on the BundleTable class. In the following example, debug is set to true in web.


1 Answers

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.

like image 193
Peter Porfy Avatar answered Sep 22 '22 00:09

Peter Porfy