CSS File Bundling Basics

To add custom CSS to an ASP.Net project, add the CSS files to /content and include them in a bundle in Bundle.config. A style bundle can contain a single style sheet or several style sheets. When in release/production, all files in each bundle will be minified and combined into a single version-stamped file.

In the example below, we’re adding a bundle containing two style sheets my-special-styles1.css and my-special-styles2.css, and we will reference the bundle on the Page or Master Page using ~/my-special-styles.

Sample Bundle.config File:

<?xml version="1.0" encoding="utf-8" ?>
<bundles version="1.0">
  <styleBundle path="~/Content/css">
    <include path="~/Content/Site.css" />
  </styleBundle>
  <styleBundle path="~/Content/my-special-styles">
    <include path="~/Content/my-special-styles1.css" />
    <include path="~/Content/my-special-styles2.css" />
  </styleBundle>
  <styleBundle path="~/Content/my-print-styles">
    <include path="~/Content/my-print-styles.css" />
  </styleBundle>
</bundles>

A warning about Bundle.config: Changes to Bundle.config are only read when the application starts, and changes to Bundle.config on their own don’t result in updated DLLs when the project is next built. So if you are working with IIS, the application won’t restart and read the updated Bundle.config file until the DLL files are changed. Easy work-around: When you change Bundle.config, also change something else (1 line of code) that will force the DLLs to be rebuilt on the next build.

Minified CSS Files

When minification occurs, which is normally only in release/production, the optimizer will automatically minify the CSS files referenced in the bundle. However, if a minified file is detected, it will use that file. When a CSS file is named my-styles.css, it will look for a minified equivalent named my-styles.min.css. The minified CSS file does not need to be listed in the bundle.

Rendering Links to Bundles on Page or Master Page

In most cases, you’ll just want the default style link rendering, which is how the file Site.css is included in the Master Page in the default WebForms project.

The Usual Way

Normal rendering of style bundles (used in default ASP.Net Master Page):

<webopt:BundleReference runat="server" Path="~/Content/css" />

You may also render the bundle by calling Styles.Render(), wich generates equivalent output to using :

<%: Styles.Render("~/Content/css") %>

Customizing the Link Tag Properties

You may need to customize properties of the link tag, especially the media property if you want to include styles spefifically for screen or printing. There are two basic ways of doing this, and my preferred method is the first of the two.

Use Styles.RenderFormat() to render links to the bundle with a custom format. Because this doesn’t force bundling in dev (unlike Styles.Url()), this is my preferred means of customizing the tag used when adding a CSS bundle.

<%: Styles.RenderFormat("<link href=\"{0}\" rel=\"stylesheet\" type=\"text/css\" media=\"print\" rel=\"stylesheet\" />", "~/Content/my-print-styles") %>

Use Styles.URL() to output the bundle URL in a plain old tag with custom format. Note: Styles.Url() always forces bundling and minification, even in dev, and even with BundleTable.EnableOptimizations=false. See more details below.

<link href="<%: Styles.Url("~/Content/my-print-styles") %>" type="text/css" media="print" rel="stylesheet" />

Force Bundling in Development for Testing

The debug property in the compilation tag in Web.config is what determines whether or not bundling should occur for an application.

<compilation debug="false" targetFramework="4.6.1" />

Setting debug="true" will result in all CSS bundles being bundled, minified, and version-stamped. (Note that Styles.Url() partly ignores this value – see details in next section.)

Another way to force bundling to occur is to set BundleTable.EnableOptimizations=true in Global.asax.cs as follows:

void Application_Start(object sender, EventArgs e) {
    // Code that runs on application startup
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    BundleConfig.RegisterBundles(BundleTable.Bundles);
    BundleTable.EnableOptimizations = false; // Force bundling during dev (FOR TESTING PURPOSES ONLY)
}

Bundling & Minification Behaviour By Method

During my testing, I found that all methods except for Styles.Url() behave the same way when it comes to outputting individual, non-minified files in debug/dev, vs. combined, minified files in release/production.

When in debug/dev (or BundleTable.EnableOptimizations=false is manually set in Global.asax):

  • webopt:BundleReference: Links to individual css files in the bundle, sent in their original format (no minification).
  • Styles.Render: Links to individual css files in the bundle, sent in their original format (no minification).
  • Styles.RenderFormat(): Links to individual css files in the bundle, sent in their original format (no minification).
  • Styles.Url(): Bundles and minifies all CSS files. Does NOT use style.min.css; only minifies normal CSS files in the bundle.

When in release/prod (or BundleTable.EnableOptimizations=true is manually set in Global.asax)

  • webopt:BundleReference: Bundles and minifies all css files. Will use the style.min.css files if provided.
  • Styles.Render: Bundles and minifies all css files. Will use the style.min.css files if provided.
  • Styles.RenderFormat(): Bundles and minifies all css files. Will use the style.min.css files if provided.
  • Styles.Url(): Bundles and minifies all CSS files. Will use the style.min.css files if provided.

References