jvance.com

.net, c#, asp.net, linq, htpc, woodworking

ASP.NET MVC Bundling of Bootstrap Less Source

Posted in ASP.NET, C#, MVC by JarrettV on 8/31/2012 8:09:00 PM - CST

Our team is utilizing bootstrap for our MVC application. We would like to be able to update the variables.less file and have the entire "theme" of the website updated.

The typical methods are:

  • Utilize a tool (Chirpy in Visual Studio) to automatically output css anytime the less file is updated
  • Transform the less on the server side

I prefer the second approach since:

  • It doesn't depend on everyone having the same tool setup
  • You don't have worry about versioning of the code generated files

The most popular library dotless utilizes HttpHandlers to translate the less to css on the server side. This requires extra configuration in the web.config.

However, the latest version of ASP.NET MVC, version 4, has support for bundling and minification. It also supports transforms.

The asp.net article shows a few simple lines of code to add a transform for less files.

using System.Web.Optimization;

public class LessTransform : IBundleTransform
{
    public void Process(BundleContext context, BundleResponse response)
    {
        response.Content = dotless.Core.Less.Parse(response.Content);
        response.ContentType = "text/css";
    }
}

Unfortunately, this method only supports simple less files. If there are any imports, like in bootstrap, the application will throw a FileNotFoundException.

{"You are importing a file ending in .less that cannot be found.":"reset.less"}

We need to be able to tell the parse method where to find the imported files. You can do that by providing a VirtualFileReader that looks in your less folder.

internal sealed class VirtualFileReader : IFileReader
{
    public byte[] GetBinaryFileContents(string fileName)
    {
        fileName = GetFullPath(fileName);
        return File.ReadAllBytes(fileName);
    }

    public string GetFileContents(string fileName)
    {
        fileName = GetFullPath(fileName);
        return File.ReadAllText(fileName);
    }

    public bool DoesFileExist(string fileName)
    {
        fileName = GetFullPath(fileName);
        return File.Exists(fileName);
    }

    private static string GetFullPath(string path)
    {
        return HostingEnvironment.MapPath("~/less/" + path);
    }
}

Now all you need to do is provide the config with the VirtualFileReader to the parse method like so:

public class LessTransform : IBundleTransform
{
    public void Process(BundleContext context, BundleResponse response)
    {
        DotlessConfiguration config = new DotlessConfiguration();
        config.MinifyOutput = false;
        config.ImportAllFilesAsLess = true;
        config.CacheEnabled = false;
        config.LessSource = typeof(VirtualFileReader);
#if DEBUG
        config.Logger = typeof(DiagnosticsLogger);
#endif
        response.Content = Less.Parse(response.Content, config);         
        response.ContentType = "text/css";
    }
}

Perfect! With just a single reference to bootstrap.less you can have your less files utilize the existing bundling and minification strategy.

public class BundleConfig
{
    public static void RegisterBundles(BundleCollection bundles)
    {
        var less = new Bundle("~/bundles/less")
            .Include("~/less/bootstrap.less")
            .Include("~/less/datepicker.less")
        less.Transforms.Add(new LessTransform());
        less.Transforms.Add(new CssMinify());
        bundles.Add(less);
...

Comments

Gravatar
Posted by Sean Killeen on 9/10/2012 12:43:42 PM - CST
I'd be interested to see the advantage of this as opposed to including the less file as it is normally and relying on something like LessJS to transform on the client-side. Is this just a question of preferences at this point? I ask because it seems like a bit of extra effort to accomplish the same transformation and I'm interested to see if there is added value here. Thanks!
Gravatar
Posted by JarrettV on 9/13/2012 8:36:18 AM - CST
@Sean, we considered client side but we are targeting low end machines. I also prefer debugging server side.
Trackback  on 10/1/2012 8:45:23 PM - CST
Gravatar
Posted by Andrew Gunn on 11/27/2012 4:16:06 AM - CST
I can't see why you'd ever use LessJS to tranfrom on the client-side. Just do it once on the server (whichever method is best for you) and send them the output. Unless you're editing the LESS files on the fly (e.g. a theme editor), why bother?
Gravatar
Posted by www.google.com/accounts/o8/id?id=AItOawkyXn4tQ7dKsFJZoLY122Goc4Mgqukm1WQ on 11/29/2012 11:17:46 AM - CST
Hi Jarrett,
Thanks for your follow-up! The point makes a lot of sense and I'm attempting to implement this Running into an issue when attempting to use this technique. For some reason, the content is always empty in my response. I have my less files in ~/Content/less and have double-checked everything I'm able to think of. If you can spare a few minutes at some point, I'd appreciate it if you could check out my gist at https://gist.github.com/4170489 and see if you can spot what's wrong with it. Thank you!
Gravatar
Posted by Drew Goodwin on 1/22/2013 2:43:04 PM - CST
@Sean there are many considerations beyond preference when deciding to use less.js on the client:
* Less.min.js is about 20k. Good for some sites, but low hanging fruit for performance or bandwidth critical sites. * Unless specifically guarding against it, compiling less on the client will cause a flash of unstyled content. * It's JavaScript and, like any code, will have bugs which you'll be more likely to catch on the server. * The client side library only supports modern browsers. * Obviously, clients without JavaScript will not be able to see styles at all (still important on some sites). to name a few.

Add Comment

Login using
Google Yahoo flickr AOL
and more
Or provide your details
Please enter your name. Please enter a valid email. Please enter a valid website.
Please supply a comment.
3.8 (4)
on 9/6/2012 10:35:11 AM - CST

Recent Entries

© Copyright 2014 Powered by AtomSite 1.3.0.0