Disabling Bundling and Minification in ASP.NET 4.5/MVC 4

March 19, 2012

Lots of people are excited about the new bundling and minification feature in the next version of ASP.NET and MVC. One major drawback I see a lot of people clamoring about is the fact that you cannot conditionally disable bundling or minification when you are in debug mode. Out of the box (and to be clear, I’m referring to the version that ships with MVC 4 beta) it’s impossible to debug your CSS and Javascript.

I expect this will change in the release version, but for now you are forced to create your own custom bundles (something you’d end up doing anyway) and conditionally check if you’re in debug mode to short-circuit the bundling/minification.

Disabling minification while in debug mode

There are several tutorials out there explaining how to create bundles and conditionally turn off minification when you are running in debug mode. It’s as simple as an #if DEBUG line and creating a transformer that does nothing. For example:

protected void Application_Start()
{
    IBundleTransform jsTransformer;
#if DEBUG
    jsTransformer = new NoTransform("text/javascript");
#else
    jstransformer = new JsMinify();
#endif

    var bundle = new Bundle("~/Scripts/js", jsTransformer);

    bundle.AddFile("~/Scripts/script1.js");
    bundle.AddFile("~/scripts/script2.js");

    BundleTable.Bundles.Add(bundle);
}

Now when you reference this javascript bundle like <script src="@System.Web.Optimization.BundleTable.Bundles.ResolveBundleUrl("~/Scripts/js")"></script> in a view it will render a single bundled and minified script when in release mode, but as a single bundled, non-minified file while in debug mode.

Disabling bundling while in debug mode

The above approach improves this situation, but I don’t think it goes far enough. If I’m going to have multiple source files, I want to debug with the same multiple source files, at least initially. It would get too confusing writing code in a several files and then debugging it in a single monolithic file.

As an experiment to see if it was possible, I ended up building a better bundler that does just what I want: bundles and minifies in release mode, but doesn’t bundle or minify when the build is set to debug.

The entire class is below, explanation to follow:

public static class BetterBundler
{
    private static bool _debug;
    const string CssTemplate = "<link href=\"{0}\" rel=\"stylesheet\" type=\"text/css\" />";

    public static void Init()
    {
#if DEBUG
        _debug = true;
#endif
        var bundle = new Bundle("~/content/css", new CssMinify());

        bundle.AddFile("~/Content/test.css");
        bundle.AddFile("~/Content/site.css");
        
        BundleTable.Bundles.Add(bundle);
    }

    public static MvcHtmlString ResolveBundleUrl(string bundleUrl)
    {
        return _debug ? BundledFiles(BundleTable.Bundles.ResolveBundleUrl(bundleUrl)) : UnbundledFiles(bundleUrl);
    }
    
    private static MvcHtmlString BundledFiles(string bundleVirtualPath)
    {
        return new MvcHtmlString(string.Format(CssTemplate, bundleVirtualPath));
    }

    private static MvcHtmlString UnbundledFiles(string bundleUrl)
    {
        var bundle = BundleTable.Bundles.GetBundleFor(bundleUrl);

        StringBuilder sb = new StringBuilder();
        var urlHelper = new UrlHelper(HttpContext.Current.Request.RequestContext);

        foreach (var file in bundle.EnumerateFiles(new BundleContext(new HttpContextWrapper(HttpContext.Current), BundleTable.Bundles, bundleUrl)))
        {
            sb.AppendFormat(CssTemplate + Environment.NewLine, urlHelper.Content(ToVirtualPath(file.FullName)));
        }

        return new MvcHtmlString(sb.ToString());
    }

    private static string ToVirtualPath(string physicalPath)
    {
        var relativePath = physicalPath.Replace(HttpContext.Current.Request.ServerVariables["APPL_PHYSICAL_PATH"], "");
        return relativePath.Replace("\\", "/").Insert(0, "~/");
    }

    public static MvcHtmlString CssBundle(this HtmlHelper helper, string bundleUrl)
    {
        return ResolveBundleUrl(bundleUrl);
    }
}

To summarize, I’m using the same technique to determine debug mode, and of course this could be extended to conditionally bundle or not based on any boolean. The interesting code is in the UnbundledFiles(string bundleUrl) method.

Currently, there is no concept of named bundles – bundles are specified simply by the virtual path of the resultant bundle. This means all our calling code in the view has to give is the virtual path of the bundle. We have to start from that and uncover all the physical files deeper within the BundleTable.

var bundle = BundleTable.Bundles.GetBundleFor(bundleUrl);

This line retrieves the bundle that we created from the BundleTable.

bundle.EnumerateFiles(new BundleContext(new HttpContextWrapper(HttpContext.Current), BundleTable.Bundles, bundleUrl))

This gets all of the physical files from the bundle.

The rest is just boilerplate code to turn those raw physical files back into relative virtual paths and into the proper html tags.

Finally, you’ll note that I have an HtmlHelper method in there, CssBundle(this HtmlHelper helper, string bundleUrl). To render a bundle link in a view, this must be used. Since the result of a bundle could be one or multiple files, I decided the simplest approach would be to allow the BetterBundler to render the full html tag itself. This could easily be changed or enhanced.

In the view:

@Html.CssBundle("~/content/css")

The Result

In release mode:

<link href="/content/css?v=7GiB-1k9Pr1JbbYY72bT3T2EOpxXf0rGPdEOXVKl5oQ1" rel="stylesheet" type="text/css" />

In debug mode:

<link href="/content/test.css" rel="stylesheet" type="text/css" />
<link href="/content/site.css" rel="stylesheet" type="text/css" />

I certainly hope this improves before being released. It’s great that ASP.NET is catching up with these features, but there are already other ones out there (Cassette, SquishIt) that support this basic and important behavior.

Until then, you’re stuck using something custom like this.

Windows Live Writer image resize problems

March 1, 2012

I recently made some CSS changes to my blog and after updating the theme in Windows Live Writer, I noticed some problems with inserting images. I would paste in a new image like normal, but as soon as I customized it, it would sometimes resize down to 2x2 pixels and never let me resize it through the GUI. I’d have a tiny image like this:

Live writer 2x2 image won't resize
Recursive images are recursive.

It turns out that Live Writer was freaking out over some CSS rules I implemented to allow dynamically resizing images. If you have any image styles that affect width or height, they will likely prevent you from using the Live Writer GUI to resize images. In my case, height:auto and max-width:100% were causing problems.

I needed these styles on my actual website, but they didn’t matter for Live Writer, so how could I tweak this?

Adjusting Live Writer’s downloaded theme files

Once I realized it was just a CSS issue, I began poking through my file system looking for where Live Writer stores the templates it downloads. There it was!

C:\Users\{username}\AppData\Roaming\Windows Live Writer\blogtemplates

You’ll see one folder inside here for each configured blog account you set up. Open one up and all of its details are revealed.

Live Writer blog templates location

I opened up one of the index.htm files and noticed the stylesheet reference got translated into a local file system reference:

<LINK rel="stylesheet" type="text/css" href="file:///C:/Users/{username}/AppData/Roaming/Windows Live Writer/blogtemplates/d4ba3d64-f601-4279-8663-e0a673470a63/5021cb23-1659-4048-b858-2f1a06a9f720/master.css">

And there it was, Live Writer's copy of my blog's CSS file. I opened it, edited it, and saved it.

img,object {
    border:none;
    width:auto\9;
    /* don't want this for Live Writer */
    /*max-width:100%;*/
    /*height:auto*/
}

After closing and re-opening Live Writer, the updated CSS took affect, and I could once again resize images from the GUI tools.

image

If you ever need to customize Live Writer’s theme files for your blog, you can easily do so. All of the files are in your Windows user profile AppData folder under Live Writer.

Flexible, dynamically resizing images with CSS

February 28, 2012

I has long been a question of mine: how can I utilize a large image that looks good in most windows, but dynamically scales down as the browser window and/or screen resolution is smaller? There may be various Javascript and other hacks to do so, but I just recently learned that you can do it with pure CSS. No Javascript, no media queries, just plain ole vanilla CSS.

Here’s a simple example on jsFiddle.

700px width image

The above image is 700x55 pixels. I have a flexible layout and several CSS media queries, but as you begin to resize your browser window you will see that the image will start to be resized automagically by your browser. It’s all because of the CSS below.

Images can be made flexible with the following CSS:

img {
    max-width: 100%;
    height: auto;
    width: auto\9; /* ie8 */
}

Just specify 100% as the max-width, and auto as the height. The width: auto\9 style is to fix an IE8 bug. Thanks to web designer wall for this excellent tutorial on responsive design. Scroll down about half-way for the part about flexible images.

You can also make videos and other embedded objects dynamically resize with the same technique:

.video embed,
.video object,
.video iframe {
    width: 100%;
    height: auto;
}

Dynamically resizing image with max width

One problem with the CSS for flexible images is that you must specify 100% as the max-width. That means you can’t directly force a maximum size on the image tag itself. To accomplish this, just put it inside a containing element that does have a fixed max-width, for example:

<div style="max-width:500px;">
    <img src="..." />
</div>

This will render an image at exactly 500px, and scale down as the viewable browser window gets smaller and smaller.

That’s all there is to it! Just 2 CSS styles and a 3rd for an IE8 fix, and you’ve got images that will expand and contract in size as the containing element changes size.

About Kurt

I'm a senior consultant at Headspring in Austin, TX. My passion is creating web-based applications that are well crafted and solve real problems for real people. Want to know more? Check out my about page.

At my sister's wedding. I routinely have to develop software with specifications less detailed than this! http://t.co/RcZDbfZvgX 11 days ago