Saturday, October 26, 2013

Bootstrap 3, LESS, Bundling, and ASP.NET MVC

Until Twitter Bootstrap v3, I would have recommend that you use dotLess to compile and bundle your LESS files. However, it is now a known issue that the current build of dotLess does not support Bootstrap 3, or more specifically that it does not support LESS 1.4; and worse yet, there is no fix in sight.

So, how can you use Bootstrap LESS with MVC?

I recommend using BundleTransformer. It is an amazingly feature rich set of extensions for System.Web.Optimization. The BundleTransformer.Less extension provides easy to use LESS transformations (already up to LESS 1.5) and bundling support that wires up straight into your pre-existing BundleCollection configuration. For more information about everything that BundleTransformer has to offer, check out this article.

Now here is how you setup Bootstrap 3 and BundleTransformer for ASP.NET:

Required NuGet Packages

  1. Twitter.Bootstrap.Less
  2. BundleTransformer.Less
  3. BundleTransformer.MicrosoftAjax
  4. JavaScriptEngineSwitcher.Msie

Web.Config

I will be the first to admit, this config looks huge; but don't worry, it's really not that bad! The NuGet packages will do most of the work for you, however you have to manually set the default minifier and less jsEngine attributes manually. (Please note that I have made those lines bold to help you find them.)

<?xml version="1.0" encoding="utf-8"?>
<configuration>
 
  <configSections>
    <sectionGroup name="bundleTransformer">
      <section name="core" type="BundleTransformer.Core.Configuration.CoreSettings, BundleTransformer.Core" />
      <section name="less" type="BundleTransformer.Less.Configuration.LessSettings, BundleTransformer.Less" />
      <section name="microsoftAjax" type="BundleTransformer.MicrosoftAjax.Configuration.MicrosoftAjaxSettings, BundleTransformer.MicrosoftAjax" />
    </sectionGroup>
    <sectionGroup name="jsEngineSwitcher">
      <section name="core" type="JavaScriptEngineSwitcher.Core.Configuration.CoreConfiguration, JavaScriptEngineSwitcher.Core" />
    </sectionGroup>
  </configSections>
  
  <appSettings>
  </appSettings>
 
  <system.web>
    <httpRuntime targetFramework="4.5" />
    <compilation debug="true" targetFramework="4.5" />
    <pages>
      <namespaces>
        <add namespace="System.Web.Helpers" />
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Routing" />
        <add namespace="System.Web.WebPages" />
      </namespaces>
    </pages>
  </system.web>
 
  <system.webServer>
    <validation validateIntegratedModeConfiguration="false" />
    <handlers>
      <add name="LessAssetHandler" path="*.less" verb="GET" type="BundleTransformer.Less.HttpHandlers.LessAssetHandler, BundleTransformer.Less" resourceType="File" preCondition="" />
    </handlers>
  </system.webServer>
 
  <bundleTransformer xmlns="http://tempuri.org/BundleTransformer.Configuration.xsd">
    <core>
      <css defaultMinifier="MicrosoftAjaxCssMinifier">
        <minifiers>
          <add name="NullMinifier" type="BundleTransformer.Core.Minifiers.NullMinifier, BundleTransformer.Core" />
          <add name="MicrosoftAjaxCssMinifier" type="BundleTransformer.MicrosoftAjax.Minifiers.MicrosoftAjaxCssMinifier, BundleTransformer.MicrosoftAjax" />
        </minifiers>
        <translators>
          <add name="NullTranslator" type="BundleTransformer.Core.Translators.NullTranslator, BundleTransformer.Core" enabled="false" />
          <add name="LessTranslator" type="BundleTransformer.Less.Translators.LessTranslator, BundleTransformer.Less" />
        </translators>
      </css>
    </core>
    <less useNativeMinification="false" ieCompat="true" strictMath="false" strictUnits="false" dumpLineNumbers="None" javascriptEnabled="true">
      <jsEngine name="MsieJsEngine" />
    </less>
  </bundleTransformer>
 
  <jsEngineSwitcher xmlns="http://tempuri.org/JavaScriptEngineSwitcher.Configuration.xsd">
    <core>
      <engines>
        <add name="MsieJsEngine" type="JavaScriptEngineSwitcher.Msie.MsieJsEngine, JavaScriptEngineSwitcher.Msie" />
      </engines>
    </core>
  </jsEngineSwitcher>
 
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="WebGrease" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-1.5.2.14234" newVersion="1.5.2.14234" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="BundleTransformer.Core" publicKeyToken="973c344c93aac60d" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-1.8.9.0" newVersion="1.8.9.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
 
</configuration>

Application Start and BundleConfig

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
 
        WebApiConfig.Register(GlobalConfiguration.Configuration);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
    }
}
 
public static class BundleConfig
{
    public const string BootstrapPath = "~/Bundles/Bootstrap";
 
    public static void RegisterBundles(BundleCollection bundles)
    {
        var commonStylesBundle = new CustomStyleBundle(BootstrapPath);
        commonStylesBundle.Orderer = new NullOrderer();
        commonStylesBundle.Include("~/Content/bootstrap/bootstrap.less");
        bundles.Add(commonStylesBundle);
    }
}

Styles.Render

@using System.Web.Optimization
@using LessBundling
@{
    ViewBag.Title = "Index";
}
@Styles.Render(BundleConfig.BootstrapPath)
<div class="container">
    <h2>Index</h2>
    <p>Examples!</p>
    <span class="glyphicon glyphicon-align-center"></span>
</div>

...and you're done!

Bundle Transformer

I want to take a moment and point out what a great solution that Andrew Taritsyn (@taritsyn) has come up with to the problem of compiling/transforming these languages. The BundleTransformer literally spins up a JavaScript engine and executes the JavaScript versions of these compilers. Thus these extensions you will always be able to use the latest official builds of frameworks like LESS or TypeScript, regardless of when things like NuGet packages are updated. The solution is just fantastic, the code is very extensible, and the whole thing is open source; I honestly don't know what more you could ask for. Thank you, sir!

Now I am off to play with BundleTransformer.TypeScript...

Shout it

Enjoy,
Tom

8 comments:

  1. Hello, Tom!

    Thanks for a great article! But there is one problem: is not recommended to use the `CssMinify` and `JsMinify` classes together with a Bundle Transformer. I will not explain why it should not do, because I have written about this in the documentation (http://bundletransformer.codeplex.com/documentation). As most simple solution to this problem I recommend to install the BundleTransformer.MicrosoftAjax (http://nuget.org/packages/BundleTransformer.MicrosoftAjax) package.

    ReplyDelete
    Replies
    1. Thanks again Andrew, I have made the appropriate updates!

      Delete
  2. Thank you very much. I do appreciate your help.

    ReplyDelete
  3. It's that MVC5 can't transform LESS files on the server. Dotless stopped working so now we must convert on the client. I feel like MVC4 is better then MVC5.

    ReplyDelete
    Replies
    1. Rodrigo, BundleTransformer is able to convert LESS on a server with MVC5.

      Delete

Real Time Web Analytics