Bundle and Minify CSS/JS using ClientDependency in Umbraco 6 MVC/Razor

by: Saurabh Nandu in: Programming tags: ASP.NET MVC, Razor, Umbraco,

This is going to be a short post on how to configure style sheets and JavaScript's bundling and minification in Umbraco 6 using ClientDependency framework in MVC. The benefits of bundling and minification are obvious as they reduce the number of HTTP requests as well as decrease the download size of resources to the browser resulting in a faster browsing experience. While Microsoft has prebuilt its own libraries to enable these features, Umbraco uses the ClientDependency framework to achieve the same. You can read more about the client dependency framework from its codeplex website, its got quite a few configurations options and you and read more about them on its website. In this post I am just going to focus on getting an initial configuration up and running since there seems to be lack of clear documentation on the topic and it took me a while too to figure how to set it up correctly.

This article assumes that you already have a installation of Umbraco 6 and have configured its rendering engine to use ASP.NET MVC for the pages.

Getting Started

The first essential step for ClientDependency to work is to ensure that debug mode is turned off in the web.config file. It’s a neat idea, when you are in debug mode, bundling and minification is not applied so you can easily debug your files and continue fixing your css/js. Only when you are ready with the final output and ready for deployment which is indicated by setting debug mode as false, it acts as a trigger to turn on ClientDependency framework to come into play. So if you are not able to see the effects of bundling and minification the first checkpoint would be to ensure that you have set the debug mode to false in the web.config file.

<system.web>
    <compilation defaultLanguage="c#" debug="false" 
      batch="false" targetFramework="4.0">
.....
...
</system.web>

Listing 1: Debug mode set as false

The listing above shows a part of the web.config file explicitly setting the debug mode as false. The next step is to configure the ClientDependency framework configuration. Umbraco has a special config file in the config folder called ClientDependency.config which contains the configuration for the ClientDependency framework. In my test, the standard config file shipped with Umbraco 6.0 did not work correctly for MVC. Using it always lead to errors and at times the assembly ClientDependency.Core.MVC was reported missing by the ASP.NET runtime. 

The solution that I found, was to replace contents of the ClientDependency.config  file with the sample configuration provided in the sample documentation on codeplex. Listing 2 shows the replaced contents of the config file.

<?xml version="1.0" encoding="utf-8"?>
<!--
Customize the configuration section as required, it is optional,
when it is not specified the defaults will be loaded. YOU DONT NEED
TO SPECIFY ALL OF THIS CONFIG, THE MINIMUM IS JUST THIS:
<clientDependency version="1" />

Each section is optional, so if you're not using Mvc, you don't need
that section and if you're not using Web Forms, you don't need the 
fileRegistration section.
Composite files are used for both types of projects.

** IMPORTANT: If you're web.config setting: compilation debug="true" is set to 'true', then composite files will NOT be enabled no matter what
-->
<clientDependency version="76" fileDependencyExtensions=".js,.css">
  <!-- 
    This section is used for Web Forms only, the enableCompositeFiles="true" is optional and by default is set to true.
    The PlaceHolderProvider is set to default, the javascriptPlaceHolderId, cssPlaceHolderId attributes are optional and default to what is listed below. If using
    this provider, then you must specify both PlaceHolder controls on your page in order to render the JS/CSS.
  -->
  <fileRegistration defaultProvider="PlaceHolderProvider">
    <providers>
      <add name="PageHeaderProvider" type="ClientDependency.Core.FileRegistration.Providers.PageHeaderProvider, ClientDependency.Core" enableCompositeFiles="true"/>
      <add name="LazyLoadProvider" type="ClientDependency.Core.FileRegistration.Providers.LazyLoadProvider, ClientDependency.Core" enableCompositeFiles="true"/>
      <add name="LoaderControlProvider" type="ClientDependency.Core.FileRegistration.Providers.LoaderControlProvider, ClientDependency.Core" enableCompositeFiles="true"/>
      <add name="PlaceHolderProvider" type="ClientDependency.Core.FileRegistration.Providers.PlaceHolderProvider, ClientDependency.Core" enableCompositeFiles="true" javascriptPlaceHolderId="JavaScriptPlaceHolder" cssPlaceHolderId="CssPlaceHolder"/>
    </providers>
  </fileRegistration>
  <!-- This section is used for MVC only -->
  <mvc defaultRenderer="StandardRenderer">
    <renderers>
      <add name="StandardRenderer" type="ClientDependency.Core.FileRegistration.Providers.StandardRenderer, ClientDependency.Core" enableCompositeFiles="true" />
      <add name="LazyLoadRenderer" type="ClientDependency.Core.FileRegistration.Providers.LazyLoadRenderer, ClientDependency.Core" enableCompositeFiles="true"/>
    </renderers>  
  </mvc>
  <!-- 
The composite file section configures the compression/combination/minification of files.
You can enable/disable minification of either JS/CSS files and you can enable/disable the 
persistence of composite files. By default, minification and persistence is enabled. Persisting files
means that the system is going to save the output of the compressed/combined/minified files
to disk so that on any subsequent request (when output cache expires) that these files don't have
to be recreated again and will be based on the persisted file on disk. This saves on processing time.
-->
  <compositeFiles defaultFileProcessingProvider="CompositeFileProcessor" compositeFileHandlerPath="~/DependencyHandler.axd">
    <!--
    File processing providers perform the file combination, compression and storage.
    Generally there would be no reason to replace.
    NOTE: The pathUrlFormat is much nicer as {dependencyId}.{version}.{type} which is the default,
    however, it is specified below with '/' as the delimiter to demonstrate using it with Cassini
    since Cassini does not support '.' chars in the path.
    --> 
    <fileProcessingProviders>
      <add name="CompositeFileProcessor"
            type="ClientDependency.Core.CompositeFiles.Providers.CompositeFileProcessingProvider, ClientDependency.Core" 
            enableCssMinify="true" 
            enableJsMinify="true" 
            persistFiles="true"     
            compositeFilePath="~/App_Data/ClientDependency" 
            bundleDomains="localhost:46329" 
            urlType="MappedId"
            pathUrlFormat="{dependencyId}/{version}/{type}"/>
    </fileProcessingProviders>
    <!-- 
    A file map provider stores references to dependency files by an id to be used in the handler URL when using the MappedId Url type
    -->
    <fileMapProviders>
      <add name="XmlFileMap" 
            type="ClientDependency.Core.CompositeFiles.Providers.XmlFileMapper, ClientDependency.Core" 
            mapPath="~/App_Data/ClientDependency"/>
    </fileMapProviders>
    <!-- 
  Defines the mime types to compress when requested by the client.
  Path is a regex selector, or a * can be used as in place of 'any'.
  Generally mime types are only set by client browsers in the request for things
  such as JSON or XML ajax requests.
  -->
    <mimeTypeCompression>
      <add type="application/json" path="^.*?/Services/.*"/>
    </mimeTypeCompression>
    <!-- 
  Defines the paths to match on to enable rogue file compression.
  Path is a regex selector, or a * can be used as in place of 'any'.
  jsExt and cssExt are comma seperated list of extensions to match to have the dependencies
  replaced with the composite file handler. You can even include ASP.Net web service JS proxies.
  -->
    <rogueFileCompression>
      <add path="*" compressJs="true" compressCss="true" jsExt=".js,asmx/js" cssExt=".css">
        <!--<exclusions>
          <add path="^.*test.aspx.*"/>
        </exclusions>-->
      </add>
    </rogueFileCompression>
  </compositeFiles>
</clientDependency>

Listing 2: Updated ClientDependency.config file

The config file above has configurations for both WebForms and MVC pages, you can read the documentation on codeplex to fine tune the configuration to meet the requirements of your project. One very powerful feature configured by the above config is persistence. Enabling persistence makes the framework save the bundled and mninified css/js to a temporary file on first run. All later requests are directly served from the static file, this ensures that CPU cycles on the server are saved at the same time response to the client becomes faster. This step can make a big impact on sites with a very high number of visitors. Another point to note is that if you look at line 14 the clientDependency tag has a version property, it is important to change the version number every time you update the css/js file. The persistence module does not detect changes to CSS/JS on the fly, so if you continue making changes in the css/js files they will not reflect! Ensure that after every change you manually update the version on the config file. The best way to tackle this issue is to ensure that you enable ClientDependency only after your JavaScript's and style sheets have stabilized or not use persistence till there is stabilization.

With the configurations setup we are ready to use this framework in our View Templates. Another hiccup is trying to make sense of how to use its HTML helpers in MVC. The documentation provided at codeplex for MVC is inadequate and keeps giving errors when used with Umbraco. Searching through several forum posts there is a solution provided in one of them, but I am not sure why that clarity has not made it back into the original documentation.

The ClientDependency framework for MVC works in two parts, in first part you need to use HTML helper methods like Html.RequireCss and Html.RequireJs to define all the CSS/JS files that are needed on your page. The documentation linked above has various overloads for these helper methods. The second part is to use the Html.RenderCssHere and Html.RenderJsHere methods within your HTML markup to generate the appropriate link and script tags. Listing 3 shows a sample Umbraco MVC template

@inherits Umbraco.Web.Mvc.UmbracoTemplatePage
@using ClientDependency.Core.Mvc;
@{
    Layout = "SystenicsBaseBase.cshtml";
    @Html.RequiresCss("~/css/Site.css");
    @Html.RequiresJs("~/scripts/jquery-1.9.1.min.js",1);
    @Html.RequiresJs("~/scripts/commonscript.js",2);
}
<!DOCTYPE html>
<html>
<head>
<title>@Umbraco.Field("sEOTitle")</title>
<meta name="description" content='@Umbraco.Field("sEODescription")'/>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, 
   initial-scale=1, maximum-scale=1" />
  
    @Html.Raw(@Html.RenderCssHere())
    @Html.Raw(@Html.RenderJsHere())
</head>
<body>
  .....
</body>
</html>

Listing 3 – Sample Umbraco Template using Html Helpers

Its important to note from code listing 3 that you need to first import the ClientDependency.Core.Mvc assembly to use its helpers. The second critical step is to ensure you use the Html.RequireCss and Html.RequireJs helpers within the initial razor block. I tried defining them elsewhere on the page and it always returns invalid markup. This is a common mistake not documented on the framework site, but it leads to everyone wasting time trying to figure it out. As you can see from the listing above I have included one style sheet and two JavaScript files to be bundled and minified. Lastly, within the HTML head tag the style sheet and JavaScript is rendered. You could alternatively load the JavaScript anywhere within your Body tag for lazy loading of JavaScript.

That’s it! Save the template and fire up the Umbraco site in your browser, you will notice that it takes very long to render the first page, but later those pages are served very fast. You can also use the Chrome in-built tools to verify that the css/js files are indeed being bundled and compressed.

I hope this short post will help you setup ClientDependency in your Umbraco setup, making your client websites super fast!