Switching to Javascript and CSS Bundling in MVC 4

2 Comments

Switching to Javascript and CSS Bundling in MVC 4

I was working an an internal project that started on MVC 2, and has recently been upgraded to MVC 4. There's a lot of house keeping to do to keep the codebase maintainable.

One custom piece of code that was implemented to make sure assets like javascript and css were not cached when a new version was released was implemented as follows:​

<link rel="stylesheet" type="text/css" href="@Url.Content("~/Content/themes/base/jquery-ui.css" + Versioning.Latest)" />        
<link rel="stylesheet" type="text/css" href="@Url.Content("~/Content/kendo/2012.2.913/kendo.common.min.css" + Versioning.Latest)" />
<link rel="stylesheet" type="text/css" href="@Url.Content("~/Content/kendo/2012.2.913/kendo.metro.min.css" + Versioning.Latest)" />
<link rel="stylesheet" type="text/css" href="@Url.Content("~/Content/Site.css" + Versioning.Latest)" />    
<script type="text/javascript" src="@Url.Content("~/Scripts/kendo/2012.2.913/jquery.min.js" + Versioning.Latest)"></script>
<script type="text/javascript" src="@Url.Content("~/Scripts/kendo/2012.2.913/kendo.all.min.js" + Versioning.Latest)"></script>
<script type="text/javascript" src="@Url.Content("~/Scripts/kendo/2012.2.913/kendo.aspnetmvc.min.js" + Versioning.Latest)"></script>
<script type="text/javascript" src="@Url.Content("~/Scripts/jquery-ui-1.9.2.min.js" + Versioning.Latest)"></script>
<script type="text/javascript" src="@Url.Content("~/Scripts/jquery.formatCurrency-1.4.0.min.js" + Versioning.Latest)"></script>   
<script type="text/javascript" src="@Url.Content("~/Scripts/date.js" + Versioning.Latest)"></script>
<script type="text/javascript" src="@Url.Content("~/Scripts/jquery.watermark.min.js" + Versioning.Latest)"></script>   
<script type="text/javascript" src="@Url.Content("~/Scripts/jquery.cross-slide.min.js" + Versioning.Latest)"></script>    
<script type="text/javascript" src="@Url.Content("~/Scripts/jquery.cookie.js" + Versioning.Latest)"></script>
<script type="text/javascript" src="@Url.Content("~/Scripts/browser.support.js" + Versioning.Latest)"></script>
<script type="text/javascript" src="@Url.Content("~/Scripts/handlebars.min.js" + Versioning.Latest)"></script>

Where Versioning.Latest is defined as follows:​

 
public class Versioning
{
    public static string Latest
    {
        get
        {
            return "?v=" + typeof(Versioning).Assembly.GetName().Version;
        }
    }
}

This works, however, it will cause a redownload of all javascript/css files when a new version is deployed, even if the file hasn't changed. Third party javascript libraries such as KendoUI, jQuery and date.js are unlikely to change (particularly if you're referencing a particular version of the library)​.

​I updated this code to use the new content bundling features that were introduced in MVC 4. Here's the steps:

1. In the NuGet Pageage Manager Console, grab Microsoft.AspNet.Web.Optimization

Install-Package Microsoft.AspNet.Web.Optimization

2. Create a BundleConfig.cs and define your bundles

public class BundleConfig
{
    public static void RegisterBundles(BundleCollection bundles)
    {
        // Scripts
        bundles.Add(new ScriptBundle("~/bundles/kendoui").Include(
            "~/Scripts/kendo/2012.2.913/jquery.min.js",
            "~/Scripts/kendo/2012.2.913/kendo.all.min.js", 
            "~/Scripts/kendo/2012.2.913/kendo.aspnetmvc.min.js"));

        bundles.Add(new ScriptBundle("~/bundles/thirdparty").Include(
           "~/Scripts/jquery-ui-*",
           "~/Scripts/jquery.formatCurrency-*",
           "~/Scripts/jquery.watermark.min.js",
           "~/Scripts/jquery.cross-slide.min.js",
           "~/Scripts/jquery.cookie.js",
           "~/Scripts/date.js",
           "~/Scripts/browser.support.js",
           "~/Scripts/handlebars.min.js"));
               
        // Stylesheets
        bundles.Add(new StyleBundle("~/Content/css").Include(
            "~/Content/themes/base/jquery-ui.css",
            "~/Content/Site.css"));

        bundles.Add(new StyleBundle("~/Content/kendoui-css").Include(
            "~/Content/kendo/2012.2.913/kendo.common.min.css",
            "~/Content/kendo/2012.2.913/kendo.metro.min.css"));
    }
}

3. Call the bundle config from Global.asax Application_Start to register the bundles

protected void Application_Start()
{
   :  
   BundleConfig.RegisterBundles(BundleTable.Bundles);
   :
}

4. Update your _layout.cshtml to reference the bundles

<link href="@Scripts.Url("~/Content/css")" rel="stylesheet" type="text/css" />
<link href="@Scripts.Url("~/Content/kendoui-css")" rel="stylesheet" type="text/css" />
<script src="@Scripts.Url("~/bundles/kendoui")" type="text/javascript"></script>
<script src="@Scripts.Url("~/bundles/thirdparty")" type="text/javascript"></script>

5. Update your (root)\web.config and Views\web.config to register the System.Web.Optimization namespace

<pages pageBaseType="System.Web.Mvc.WebViewPage">
  <namespaces>
    <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.Optimization" />
  </namespaces>
</pages>

6. And you're done!​

​Run your application and look at the generated URLs for your CSS and JS. They contain a hash in the URL that updates when the bundle changes:

<link href="/Content/css?v=DW4VA8QHWz0lpg-1xgEkBu9a2jl0u21oskVI8gjXdO81" rel="stylesheet" type="text/css" />
<link href="/Content/kendoui-css?v=NOcY8mlFVB5YWf-VVYSPB17Ov0AXjKcYHOg76QV3vqQ1" rel="stylesheet" type="text/css" />
        
<script src="/bundles/kendoui?v=YbNJGKtU_KjafyxKAw3U4QVNX021Qk1BCIWYBvui2n41" type="text/javascript"></script>
<script src="/bundles/thirdparty?v=kArVyDzIkXBQ9DtFhv4FWSKAx1YfHKB4UlIG_6cJmVY1" type="text/javascript"></script>

​If we update the Site.css and rebuild, only the /Content/css has changes, all the other hashes remain the same.

<link href="/Content/css?v=tynpQ3xqzP2PGlfCEDcWZa9rnxNaQPSPSBl2yxmF_gI1" rel="stylesheet" type="text/css" />
<link href="/Content/kendoui-css?v=NOcY8mlFVB5YWf-VVYSPB17Ov0AXjKcYHOg76QV3vqQ1" rel="stylesheet" type="text/css" />
        
<script src="/bundles/kendoui?v=YbNJGKtU_KjafyxKAw3U4QVNX021Qk1BCIWYBvui2n41" type="text/javascript"></script>
<script src="/bundles/thirdparty?v=kArVyDzIkXBQ9DtFhv4FWSKAx1YfHKB4UlIG_6cJmVY1" type="text/javascript"></script>

2 Comments

Cleaning up your client side javascript with a templating engine

Comment

Cleaning up your client side javascript with a templating engine

​Today, I was working on a code base for a time sheeting system. It does a lot of rendering on client side like retrieving appointments and source code comments from third party system and then renders it onto the page.

​Unfortunately, the javascript that did the rendering post ajax call was something like this:

for (var i = 0; i < wi.CheckinHistory.length; i++) {
history += "<li><i class=\"add-note\" title=\"Add this checkin comment to the Notes field\">" + wi.CheckinHistory[i] + "</i></li>";
}
var item = "<span class=\"wi-link\"><a href=\"" + wi.Url + "\" target=\"_blank\">[" + wi.Id + "]</a></span>" +
"<span class=\"wi-title\">" + wi.Title + "</span>" +
"<span class=\"wi-state\">" + wi.State + "</span>" +
"<span class=\"wi-project\">" + wi.ProjectName + "</span>" +
"<span class=\"wi-checkins\"><ul>" + history + "</ul></span>" +
"<span class=\"wi-modified\">" + "(Last Modified " + modDate + " by " + wi.ChangedBy + ")</span>";
return item;

​This is obviously not maintainable as it's not really readable and prone to error. 

I refactored this code and re-implemented it using a javascript templating engine. There are several around and LinkedIn has a good smackdown of several options

I chose to use Handlebars​ because of the easy to read syntax and extensible (but mainly because it has an awesome logo).

Installing and using Handlebars

Grab it from NuGet using :

Install-Package Handlebars.js

​Reference the handlebars.min.js in your layout/master page.

Create your template, the javascript that builds html above will become nice and readable:​

    <script id="tfsChangeSetTemplate" type="text/x-handlebars-template">
<h3>Changesets ({{count result}})</h3>
{{#each result}}
<div class="wi-item">
<span class="wi-link"><a href="{{Url}}" target="_blank">[Changeset {{Id}}]</a></span>
<span class="wi-title">Checked In {{CommitDateFormatted}} by {{CommittedBy}} </span>
<span class="wi-state">{{Changes}} file(s) changed</span>
<span class="wi-checkins">
<ul>
<li>{{#if Comment}}
<i class="add-note" title="Add this checkin comment to the Notes field">{{html Comment}}
</i>
{{else}}
<i>No comment was entered
</i>
{{/if}}
</li>
</ul>
</span>
{{#if WorkItems}}
<span class="wi-workitems">
<ul style="list-style: none; margin-left: 20px;">
{{#each WorkItems}}
<li>
<span class="wi-link"><a href="{{Url}}" target="_blank">[WI {{this.Id}}]</a></span>
<span class="wi-title">{{Title}}</span>
<span class="wi-state">{{State}}</span>
<span class="wi-project">{{ProjectName}}</span>
</li>
{{/each}}
</ul>
</span>
{{/if}}
</div>
{{/each}}


</script>

You'll notice I put in some conditional logic using the IF block and made extensive use of the EACH block to loop through the JSON data.​

The javascript to get and render the template is:​

if (data.length > 0) {
var tfsWorkItemTemplate = Handlebars.compile($("#tfsChangeSetTemplate").html());
var html = tfsWorkItemTemplate({ result: data });
$tasks.append(html);
}

Nice and simple!​

Comment

1 Comment

Running a stand-up meeting using Team Foundation Service (tfspreview)

Here at SSW, we've been transitioning all our new projects onto Team Foundation Service. It’s been a pleasure to work with in all aspects of the application lifecycle. It’s main features are:

  • A great UI for managing the tasks in a sprint
  • Cloud based hosting, so you don’t have to worry about upgrades and server hardware
  • It connects to Windows Live for authentication so you don’t need to create an Active Directory account for your clients

Below is a video of how Microsoft use Team Foundation Service to run their internal scrum teams. It provides great insight into how the tool works, but also shows you how a standup meeting should run. The key takeaways for me are:

  • Stand-up meeting happens in a corridor
  • Each team member can easily switch the taskboard to their view (showing what they’ve worked on)
  • “Parking” issues so the stand-up is quick

http://tv.ssw.com/1279/daily-scrum-at-microsoft Everybody knows the 3 essential questions.... lets go well beyond that. See the TFS Agile Team do a Daily Scrum What did they do right? ...and wrong? What do you do better? Please tweet me @AdamCogan and give me feedback @AdamCogan www.adamcogan.com Based on tips from the SSW Rule: Do you have daily stand-up meetings (aka a Scrum)?

Check out Methodology - Do you do Daily Scrums (aka stand-up meetings)? for more details about how SSW runs scrums

1 Comment

4 Comments

CRM 2011 Outlook Client - Stuck in reboot loop installing SQL Express

Ran into this issue today when trying to install the Microsoft Dynamics CRM 2011 for Microsoft Office Outlook where the installer would constantly ask for a reboot when trying to install “Microsoft SQL Server Express Edition 2008 (CRM)”. No matter how many times you rebooted it would always ask to reboot again. Note: This only happens if you use the “Offline” functionality.

SNAGHTML26bc61

The solution to this is to manually install Microsoft SQL Server 2008 R2 Express and call the instance “CRM”. This allows CRM to continue and skip over the installation of SQL Server Express.

SNAGHTML324320

Hope this helps others who get stuck with the install.

4 Comments