Web farms in .NET and IIS part 2: Network Load Balancer

This post is direct continuation of the post on the general introduction into web farms. We briefly mentioned NLB in the previous post: it performs load balancing by manipulating the MAC address of the network adapters.

This product is in fact not the primary choice for load balancing nowadays. It is very easy to use but it lacks many features that a full blown load balancer has but it has its place, especially in conjunction with Application Request Routing ARR – more on that in the next post.

Web traffic is routed to all servers in the web farm but only one of them will actually respond while the others simply ignore the request. Therefore NLB doesn’t have any load balancing device in front of the web farm – the farm machines work together to “solve an issue”.

Network load balancer

You first need to activate NLB on each machine in the web farm. If you have Windows Server 2008 R2 then in the Start menu select Administrative Tools and then Server Manager. In the Server Manager window select the Features node and click the Add Features menu point in the Action menu. Select NLB in the list that appears:

NLB activation

Click Install and if the installation was successful then you’ll be greeted with the following message:

NLB activation success

The NLB manager will then be available through a simple search:

NLB among search results

I don’t actually have access to 2 or more private machines with Windows Server installed and I don’t want to misuse my company’s network either for my private blogging purposes so it’s probably best to point you to a video showing how to set up NLB. I’d suggest you to watch the video to get a general idea but make sure you read my comments below before you actually set up the NLB cluster:

How to set up NLB – a Youtube video

Let’s add some information to the setup process:

In the New Cluster: Host Parameters window you can choose the default state of the host when it comes online: started, stopped or suspended. Started is a good option of course. The ‘Retain suspended state after computer restarts’ checkbox is asking you if the host should be put into a suspended state in case it is rebooted. This is an interesting feature: say that you need to update one of the hosts and the updates require a machine restart. You may not want the host to jump back online again as the changes you made may be breaking ones that need to be tested first.

In the same window you can set the priority of the cluster member. Normally the first one you create will have priority 1, the next one will be priority 2 and so on. Keep in mind that the servers normally function as peers to each other so the default 1,2…n increments are fine.

In the New Cluster: Cluster Parameters window you need to select the Cluster operation mode: unicast, multicast or IGMP multicast. Normally it’s going to be unicast but in a virtual environment you’ll probably need to select multicast. If your network supports multicast then either of the multicast types will be the best solution. Make sure to ask your network engineers in the office if you’re not sure which option is applicable to you.

In the same window you can add a Full internet name. This is optional, you can even leave it blank. It gives you an administrative name for the NLB cluster.

In the New Cluster: Port Rules window you can define which ports will be balanced:

NLB port rules

Click ‘Edit’ and the Add/Edit Port Rule window will open:

NLB port rules

As this is a web farm you may as well choose to load balance port 80 only so set 80 in the From and To port range option:

NLB from port 80 to port 80

We are dealing with TCP so you may as well select TCP from the Protocols options:

NLB Tcp

In the same window you select affinity under the Filtering mode options – this becomes available because we select the Multiple host option as we have 2 hosts as opposed to the Single host option. The choices for affinity are none, single or network. Normally the default choice is None. This means that when a request comes from the certain IP address and we get a new request from the IP address then we don’t care which web server the client is routed to.

This is what you should aim for: no sticky sessions. You can store the session state on a different machine – this is a topic I’ll take up in this series on web farms. Avoiding sticky sessions makes load balancing more effective. With sticky sessions the load balancer is constrained to direct the same user to the same web server even if that server is under heavy load so we lose some of the efficiency of load balancing. The option ‘Single’ means that the user with the same IP address should always be directed to the same machine. The ‘Network’ option is the same as ‘Single’ but for a subgroup of users. This last option is very rarely used.

You can add another Port Rule for SSL: the From and To Port Range options will be set to 443.

All requests coming to a port not defined in this window will be ignored because NLB will not know how to load balance it.

Now you can press finish and the NLB manager will start creating our cluster. You’ll see that the status is Pending at first:

NLB cluster creation pending

Eventually the status will change to Converged which means we can add a new web server: right click the cluster name, which should be the name you provided in the Full internet name text box, and choose Add Host to Cluster which will lead you through the same windows as when you set up the first node above. Many values such as the port rules will already be populated based on your previous entries. The NLB manager will add this node to the cluster when you’re finished setting it up.

At the end of this process you should see two Converged web farm members in the NLB manager window. You can verify the setup by pinging the name of the cluster you chose in the Full internet name text box. Enter the following in the command prompt: ping
You should see that it answers with the IP of the cluster that you see in the NLB manager, e.g. nlb.mysite.com (123.456.789.10). You can now also navigate to nlb.mysite.com in a web browser and it will probably take you to the first priority web farm member, but this is not for certain: NLB can of course route the request to whichever member in the cluster. In case you set up the system with affinity then you should see that you get to the same cluster member upon subsequent requests.

Keep in mind that NLB has no concept of health checks so it will route to any member of the web farm regardless of the state of the members. Even if a bad server responds with a 503 NLB will not take any note of that and will happily route the request to that machine. This is especially harmful if you’ve set up affinity and the user is always directed to the same machine in the immediate future.

You can easily temporarily remove a cluster member, e.g. if you want to perform some patch on it. Right-click the cluster member node in the NLB manager, click Control Host and select Stop or Drainstop. Stopping means that the node will not accept any connections. Drainstop means that the node will not accept any new connections.

In the above scenario with 2 web farm members stopping one of the two nodes will make NLB route all the web traffic to the one available machine. That’s basically a very simple solution for high availability. NLB handles server failures automatically – when one fails then it does not take part in the decision process on which server should handle the request.

This concludes the introduction to NLB. In the next post we’ll look at ARR.

Web farms in .NET and IIS part 1: a general introduction

Introduction

In this series I’ll try to give you an overview of web farms in the context of IIS and .NET. The target audience is programmers who want to get started with web farms and the MS technologies built around them. I used IIS 7.5 and .NET4.5 in all demos but you should be fine with IIS7.0 and .NET4.0 as well and things should not be too different in IIS8.0 either.

What is a web farm?

A web farm is when you have two servers that perform the same service. You make an exact copy of an existing web server and put a load balancer in front of them like this:

Web farms basic diagram

It is the load balancer that catches all web requests to your domain and distributes them among the available servers based on their current load.

The above structure depicts the web farm configuration type called Local Content. In this scenario each web farm machine keeps the content locally. It is up to you or your system administrator to deploy the web site to each node after all the necessary tests have been passed. If the web site writes to a local file then the contents of that file should be propagated immediately to every node in the web farm.

With Local Content the servers are completely isolated. If something goes wrong with one of them then the system can continue to function with the other servers up and running. This setup is especially well suited for distributing the load evenly across the servers.

Disadvantages include the need for an automated content replication across servers which may become quite complicated if you have many elements to replicate: web content, certificates, COM+ objects, GAC, registry entries etc. Also, as mentioned above, if the web site writes to disk then the contents of that file must be propagated to the other nodes immediately. You can alternatively have a file share but that introduces a single point of failure so make sure it is redundant.

Local Content is probably the most common solution for many high traffic websites on the Internet today. There are other options though:

  • Shared network content, which uses a central location to manage the content where all web servers in the farm point to that location
  • Shared Storage Area Network (SAN) or Storage Spaces in Windows Server 2012, which allow the storage space to be attached as a local volume so that it can be mounted as a drive or a folder on the system

We’ll concentrate on the Local Content option as it is the easiest to get started with and it suits most web farm scenarios out there. If you’re planning to build the next Google or Facebook then your requirements are way beyond the scope of this post anyway: take a look at the web farming frameworks by Microsoft mentioned at the very end of this post. They are most suitable for large websites, especially Windows Azure Services.

Why use a web farm?

The main advantage is reliability. The load balancer “knows” if one of the web servers is out of service, due to maintenance or a general failure, it doesn’t matter, and makes sure that no web request is routed to that particular server. If you need to patch one of the servers in the farm you can simply temporarily remove it from the farm, perform the update and then bring the server up again:

One server off

You can even deploy your web deployment package on each server one after the other and still maintain a continuous service to your customers.

The second main advantage of a web farm is to be able to scale up the web tier. In case you have a single web server and you notice that it cannot handle the amount of web traffic you can copy the server so that the load will be spread out by the load balancer. The servers don’t have to be powerful machines with a lot of CPU and RAM. This is called scaling out.

By contrast scaling out the data tier, i.e. the database server has been a lot more difficult. There are available technologies today that make this possible, such as NoSql databases. However, the traditional solution to increase the responsiveness of the data tier has been to scale up – note ‘up’, not ‘out’ – which means adding more capacity to the machine serving as the data tier: more RAM, more CPU, bigger servers. This approach is more expensive than buying more smaller web machines, so scaling out has an advantage in terms of cost effectiveness:

Data tier vs web tier

Load balancers

How do load balancers distribute the web traffic? There are several algorithms:

  • Round-robin: each request is assigned to the next server in the list, one server after the other. This is also called the poor man’s load balancer as this is not true load balancing. Web traffic is not distributed according to the actual load of each server.
  • Weight-based: each server is given a weight and requests are assigned to the servers according to their weight. Can be an option if your web servers are not of equal quality and you want to direct more traffic to the stronger ones.
  • Random: the server to handle the request is randomly selected
  • Sticky sessions: the load balancer keeps track of the sessions and ensures that return visits within the session always return to the same server
  • Least current request: route traffic to the server that currently has the least amount of requests
  • Response time: route traffic to the web server with the shortest response time
  • User or URL information: some load balancers offer the ability to distribute traffic based on the URL or the user information. Users from one geographic location region may be sent to the server in that location. Requests can be routed based on the URL, the query string, cookies etc.

Apart from algorithms we can group load balancers according to the technology they use:

  • Reverse Proxy: a reverse proxy takes an incoming request and makes another request on behalf of the user. We say that the Reverse Proxy server is a middle-man or a man-in-the-middle in between the web server and the client. The load balancer maintains two separate TCP connections: one with the user and one with the web server. This option requires only minimal changes to your network architecture. The load balancer has full access to the all the traffic on the way through allowing it to check for any attacks and to manipulate the URL or header information. The downside is that as the reverse proxy server maintains the connection with the client you may need to set a long time-out to prepare for long sessions, e.g. in case of a large file download. This opens the possibility for DoS attacks. Also, the web servers will see the load balancer server as the client. Thus any logic that is based on headers like REMOTE_ADDR or REMOTE_HOST will see the IP of the proxy server rather than the original client. There are software solutions out there that rewrite the server variables and fool the web servers into thinking that they had a direct line with the client.
  • Transparent Reverse Proxy: similar to Reverse Proxy except that the TCP connection between the load balancer and the web server is set with the client IP as the source IP so the web server will think that the request came directly from the client. In this scenario the web servers must use the load balancer as their default gateway.
  • Direct Server Return (DSR): this solution runs under different names such as nPath routing, 1 arm LB, Direct Routing, or SwitchBack. This method forwards the web request by setting the web server’s MAC address. The result is that the web server responds directly back to the client. This method is very fast which is also its main advantage. As the web response doesn’t go through the load balancer, even less capable load balancing solutions can handle a relatively large amount of web requests. However, this solution doesn’t offer some of the great options of other load balancers, such as SSL offloading – more on that later
  • NAT load balancing: NAT, which stands for Network Address Translation, works by changing the destination IP address of the packets
  • Microsoft Network Load Balancing: NLB manipulates the MAC address of the network adapters. The servers talk among themselves to decide which one of them will respond to the request. The next blog post is dedicated to NLB.

Let’s pick 3 types of load balancers and the features available to them:

  • Physical load balancers that sit in front of the web farm, also called Hardware
  • ARR: Application Request Routing which is an extension to IIS that can be placed in front of the web tier or directly on the web tier
  • NLB: Network Load Balancing which is built into Windows Server and performs some basic load balancing behaviour

Load balancers feature comparison

No additional failure points:

This point means whether the loadbalancing solution introduces any additional failure points in the overall network.

Physical machines are placed in front of your web farm and they can of course fail. You can put a multiple of these to minimise the possibility of a failure but we still have this possible failure point.

With ARR you can put the load balancer in front of your web farm on a separate machine or a web farm of load balancers or on the same web tier as the web servers. If it’s on a separate tier then it has some additional load balancing features. Putting it on the same tier adds complexity to the configuration but eliminates additional failure points, hence the -X sign in the appropriate cell.

NLB runs on the web server itself so there are no additional failure points.

Health checks

This feature means whether the load balancer can check whether the web server is healthy. This usually means a check where we instruct the load balancer to periodically send a request to the web servers and expect some type of response: either a full HTML page or just a HTTP 200.

NLB is only solution that does not have this feature. NLB will route traffic to any web server and will be oblivious of the answer: can be a HTTP 500 or even no answer at all.

Caching

This feature means the caching of static – or at least relatively static – elements on your web pages, such as CSS or JS, or even entire HTML pages. The effect is that the load balancer does not have to contact the web servers for that type of content which decreases the response times.

NLB does not have this feature. If you put ARR on your web tier then this feature is not available really as it will be your web servers that perform caching.

SSL offload

SSL Offload means that the load balancer will take over the SSL encryption-decryption process from the web servers which also adds to the overall efficiency. SSL is fairly expensive from a CPU perspective so it’s nice to relieve the web machine of that responsibility and hand it over to the probably lot more powerful load balancer.

NLB doesn’t have this feature. Also, if you put ARR on your web tier then this feature is not available really as it will be your web servers that perform SSL encryption and decryption.

A benefit of this feature is that you only have to install the certificate on the load balancer. Otherwise you must make sure to replicate the SSL certificate(s) on every node of the web farm.

If you go down this path then make sure to go through the SSL issuing process on one of the web farm servers – create a Certificate Signing Request (CSR) and send it to a certificate authority (CA). The certificate that the CA generates will only work on the server where the CSR was generated. Install the certificate on the web farm server where you initiated the process and then you can export it to the other servers. The CSR can only be used on one server but an exported certificate can be used on multiple servers.

There’s a new feature in IIS8 called Central Certificate Store which lets you synchronise your certificates across multiple servers.

Geo location

Physical loadbalancers and ARR provide some geolocation features. You can employ many load balancers throughout the world to be close to your customers or have your load balancer point to different geographically distributed data centers. In reality you’re better off looking at cloud based solutions or CDNs such as Akamai, Windows Azure or Amazon.

Low upfront cost

Hardware load balancers are very expensive. ARR and NLB are for free meaning that you don’t have to pay anything extra as they are built-in features of Windows Server and IIS. You probably want to put ARR on a separate machine so that will involve some extra cost but nowhere near what hardware loadbalancers will cost you.

Non-HTTP traffic

Hardware LBs and NLB can handle non-HTTP traffic whereas ARR is a completely HTTP based solution. So if you’re looking into possibilities to distribute other types of traffic such as for SMTP based mail servers then ARR is not an option.

Sticky sessions

This feature means that if a client returns for a second request then the load balancer will redirect that traffic to the same web server. It is also called client affinity. This can be important for web servers that store session state locally so that when the same visitor comes back then we don’t want the state relevant to that user to be unavailable because the request was routed to a different web server.

Hardware LBs and ARR provide a lot of options to introduce sticky sessions including cookie-based solutions. NLB can only perform IP-based sticky sessions, it doesn’t know about cookies and HTTP traffic.

Your target should be to avoid sticky sessions and solve your session management in a different way – more on state management in a future post. If you have sticky sessions then the load balancer is forced to direct traffic to a certain server irrespective of its actual load, thus beating the purpose of load distribution. Also, if the server that received the first request becomes unavailable then the user will lose all session data and may receive an exception or unexpected default values in place of the values saved in the session variables.

Other types of load balancers

Software

With software load balancers you can provide your own hardware while using the vendor-supported software for load balancing. The advantage is that you can provide your own hardware to meet your load balancing needs which can save you a lot of money.

We will in a later post look at Application Request Routing (ARR) which is Microsoft’s own software based reverse proxy load balancer which is a plug-in to IIS.

Another solution is HAProxy but it doesn’t run on Windows.

A commercial solution that runs on Windows is KEMP LoadMaster by KEMP Technologies.

Frameworks

There are frameworks that unite load balancers and other functionality together into a cohesive set of functions. Web Farm Framework and Windows Azure Services are both frameworks provided by Microsoft that provide additional functionality on top of load balancing. We’ll look at WFF in a later post in more depth.

How to bundle and minify CoffeeScript files in .NET4.5 MVC4 with C#

In the previous blog post I showed you how the bundling and minification mechanism in MVC4 works with CSS and JavaScript files. Some of you may have CoffeeScript files in their projects and you may be wondering if they can be bundled as well. The answer is yes, and it’s not complicated at all.

Add a new folder to your MVC4 application called CoffeeScript. Add a file called customers.coffee to it and insert the following CoffeeScript code:

class Customer
	constructor: (@name) ->

	complain: ->
		alert @name + " says: WHERE IS MY ORDER???!!!!"

cust = new Customer("Angry customer")
cust.complain()

This should not be too complicated I believe: we construct a Customer object and call its complain() method.

The next step is to import a package called CoffeeBundler from NuGet: PM> install-package CoffeeBundler. This will add the necessary DLLs to your projects. Now we have access to another type of bundler which will translate CoffeeScript into JavaScript and also perform the bundling and minification processes.

Next go to the BundleConfig.RegisterBundles method and add the following bit of code:

Bundle coffeeBundler = new ScriptBundle("~/bundles/coffee")
                .Include("~/CoffeeScript/customers.coffee");
            coffeeBundler.Transforms.Insert(0, new CoffeeBundler.CoffeeBundler());
            bundles.Add(coffeeBundler);

As CoffeeScript is nothing but JavaScript we still need to create a ScriptBundle object and include our CoffeeScript file in it. We also need to add a transform which will translate the CoffeeScript files into JavaScript, compile them and bundle/minify them. We also want to make sure that this is the first transformation that runs, hence the 0 in the Insert method. By default the JS bundler is the first in the transformation process.

Now let’s open _Layout.cshtml and insert the following bit of code right before the closing /body tag:

<script src="@BundleTable.Bundles.ResolveBundleUrl("~/bundles/coffee")"></script>

In case you do a build now you may receive some errors that complain about a file called AppStart_RegisterCoffeeBundler.cs. This was added automatically when we inserted the CoffeeBundler package from NuGet. You can safely delete this file; its automatically generated code had not quite caught up with .NET4.5 when writing this blog post.

The reason we use the longer script tag to call the ResolveBundleUrl instead of the shorter Scripts.Render is the same: the CoffeeBundler is a bit behind the developments in .NET4.5 and it’s safer to use the longer version.

Now run the application. If all went well then you should see a standard JavaScript alert stating that there’s an angry customer. Open the source view to check the generated HTML. You should see that the CoffeeScript bundle has been created:

<script src="/bundles/coffee?v=_Jtpxzv6QVFgvnfRHCOt5_bHmKJW0D27L4nwCa0u1gU1"></script>

Call that bundle from the browser, the address should be http://localhost:xxxx/bundles/coffee and you should see the JavaScript that the CoffeeScript bundler has generated:

(function(){var n,t;n=function(){function n(n){this.name=n}return n.prototype.complain=function(){return alert(this.name+" says: WHERE IS MY ORDER???!!!!")},n}(),t=new n("Angry customer"),t.complain()}).call(this)

So, simple as that!

View the list of MVC and Web API related posts here.

Web optimisation: resource bundling and minification in .NET4.5 MVC4 with C#

We will look at the bundling and minification techniques available in a .NET4.5 MVC4 project. Bundling and minification help optimise a web page by reducing the number of requests sent to the server and the size of the downloads. I assume most readers will know what the terms mean, but here comes a short explanation just in case.

Bundling: if your websites requires resource files, typically CSS and JavaScript files then the browser will have to request them from the server. Ideally these resources should be bundled so that the browser can receive the external files in a lower number of requests: all CSS content will be copied to one single CSS file – a CSS bundle – and the same can be made to the JS files creating a JS bundle.

Minification: it is obvious that larger external files take longer to download. CSS and JS files can usually be made smaller by removing comments, white space, making variable names shorter etc.

The two techniques applied together will decrease the page load time making your readers happy.

These two web optimisation techniques are well-known in the web developing world but they are generally cumbersome to carry out manually. Many programmers simply omit them due to time constraints. However, MVC4 in .NET4.5 has made it extremely straightforward to perform bundling and minification so there should be no more excuses.

For the demo project start Visual Studio 2012 and create an MVC4 internet application with .NET4.5 as the underlying framework. Navigate to Index.cshtml of the Home view and reduce its contents to the following:

@{
    ViewBag.Title = "Home Page";
}
<h2>Bundling and minification</h2>
<div>Welcome to bundling and minification.</div>

If you go to the Contents folder you’ll see a default CSS file called Site.css. Add two more CSS files to the folder to simulate tweaking styles: mystyle.css and evenmorestyle.css. For the sake of simplicity I simply copied the contents of Site.css over to the two new files but feel free to fill them with any other css content. Remember, we only pretend that we need 3 style sheets to render our website, we will not really need them.

If you take a look at the Scripts folder it includes a lot of default scripts: jQuery, knockout, modernizr. We will pretend that our site needs most of them.

Locate _Layout.cshtml in the Views/Shared folder. Note that the _Layout view will already include some default bundling and minification features but we’ll start from scratch to make the transition from no optimisation to more optimisation more obvious. So update the contents of _Layout.cshtml to the following – note that you can drag and drop the CSS and JS files onto the editor, VS will create the ‘link’ and ‘script’ tags for you:

<!DOCTYPE html>
<html lang="en">
    <head>        
        <title>@ViewBag.Title - My ASP.NET MVC Application</title>
        <link href="~/Content/Site.css" rel="stylesheet" />
        <link href="~/Content/mystyle.css" rel="stylesheet" />
        <link href="~/Content/evenmorestyle.css" rel="stylesheet" />
        <script src="~/Scripts/modernizr-2.5.3.js"></script>
    </head>
    <body>
        <div>
            @RenderBody()
        </div>
        <script src="~/Scripts/jquery-1.8.3.js"></script>
        <script src="~/Scripts/jquery-ui-1.9.2.js"></script>
        <script src="~/Scripts/jquery.unobtrusive-ajax.js"></script>
        <script src="~/Scripts/jquery.validate.js"></script>
        <script src="~/Scripts/jquery.validate.unobtrusive.js"></script>
        <script src="~/Scripts/knockout-2.1.0.js"></script>
    </body>
</html>

As you see our page needs a lot of external files. You may be wondering why modernizr.js is included separately from the other JS files. The reason is because modernizr.js makes non-HTML5 compatible browsers “understand” the new tags available in HTML5 such as “section” or “aside”. Therefore it needs to be loaded before the HTML appears otherwise its effects will not be seen on the rendered HTML.

You will probably know why JS files are included at the bottom of HTML code: if they come first then the browser will not start rendering the HTML until the JS files have been downloaded. By downloading them as late as possible we can increase the perceived response time. The version numbers of the jQuery files may not be the same as in your solution; I ran an update-package command in the package manager to have the latest version of each. It does not really matter for our discussion though so it’s OK to include the files that Visual Studio inserted by default.

Run the application in Internet Explorer and check the HTML source:

Page source with no optimisation

No surprises here: there is no bundling or minification going on here.

While IE is still open press F12 to turn on the developer tools. Go to the Network tab:

Select Network in developer tools

Clear the browser cache in Internet Options to simulate a user who comes to our site for the first time, press “Start capturing” in web developer tools – marked with an underline above – and refresh the page. The developer tool will capture the rendering time of each component of the home page. You may see something like the following:

Developer tools showing uncached response times without optimisation

The response times on your screen may of course differ but the point is that each resource stands on its own, they are not bundled or minified in any way. Of course some will be downloaded in parallel by the browser – the exact number of parallel connections will depend on the browser type. This number is 6 in IE8 and higher. However, we can do a lot better. Click ‘Go to detailed view’ and select the Timings tab to see the total rendering time. It may look something like this:

Rendering time with no optimisation

The total rendering time in my case was 0.82 sec.

If you go back to the summary view of the developer tools you’ll see in the bottom of the page that the total size of the downloaded content was about 0.9MB.

Our goal is to reduce both the rendering time and the size of the resources to be downloaded. Due to the limited number of parallel requests if you have a lot of resources on your page then some of them will need to wait for other resources to be downloaded. This further increases the response time. If you highlight knockout.js in the web developer tools output and then go to the detailed view, select the Timings tab you’ll see a column called ‘offset’. These values will tell you how much time has passed since the original request:

Start time offset without optimisation

In my case knockout.js started to download 31ms after the original request.

The tools needed to optimise our page are included in the References folder: System.Web.Optimization and WebGrease. WebGrease is a low level command line utility that the framework uses under the hood to carry out the minification and bundling of our external resources.

Locate Global.asax.cs and check the code that’s found there by default. You will see a call to BundleConfig.RegisterBundles. Select ‘RegisterBundles’ and press F12 to go to the source. This is the place where the BundleCollection object is filled with our bundles. As you can see the MVC4 internet template builds some of the bundling for us.

Each type of external resource will have its own BundleType: CSS -> StyleBundle, JS -> ScriptBundle. Both objects will take a string parameter in their constructor: a virtual path to the bundled resource. The constructor is followed by a call to the Include method where we pass in the real paths to the external files to be included in that bundle. Example:

bundles.Add(new StyleBundle("~/Content/themes/base/css").Include(
                        "~/Content/themes/base/jquery.ui.core.css",
                        "~/Content/themes/base/jquery.ui.resizable.css")

The bundle represented by the virtual path ‘/Content/themes/base/css’ will include 2 jQueryUi files. When the browser requests a resource with that path MVC will intercept the request, collect the contents of the files into one consolidated resource and sends that consolidated resource as response to the browser.

The Include() method is quite clever:
– it understands the * wildcard. Example: “~/Scripts/jquery.validate*” will include all resources within the Scrips folder that start with ‘jquery.validate’.
– it will not include redundant files. Example: you may have 3 files that start with jQuery. These are jQuery.js, jQuery.min.js and jQuery.vsdoc for VS intellisense. Include() will apply conventions to exclude the ‘min’ and ‘vsdoc’ files even if you specified with a ‘*’ charater that you want all files whose name starts with ‘jQuery’. Include() will only select the ‘normal’ jQuery.js file. This way we will avoid duplicated downloads.

So let’s bundle our CSS files first. You can erase the default code in the RegisterBundles call to start from scratch:

public static void RegisterBundles(BundleCollection bundles)
        {
            
        }

Knowing what we know now let’s try to construct a bundle of our 3 CSS files:

public static void RegisterBundles(BundleCollection bundles)
        {
            bundles.Add(new StyleBundle("~/bundles/css")
                .Include("~/Content/Site.css", "~/Content/mystyle.css", "~/Content/evenmorestyle.css"));
        }

Next up is our JS files. You may recall that modernizr.js stands on its own before any HTML is rendered so we will build a bundle for that resource as well. You may be wondering why we want to have a bundle with one file and the answer is that bundling also performs minification at the same time. We’ll see how that works in practice later in this post.

Extend the RegisterBundles call as follows:

public static void RegisterBundles(BundleCollection bundles)
        {
            bundles.Add(new StyleBundle("~/bundles/css")
                .Include("~/Content/Site.css", "~/Content/mystyle.css", "~/Content/evenmorestyle.css"));

            bundles.Add(new ScriptBundle("~/bundles/modernizr")
                .Include("~/Scripts/modernizr-*"));
        }

The ‘*’ wildcard will make sure that if we update this file to a newer version in the future the bundle will include that automatically. We don’t need to come back to this code and update the version number.

The last bundle of files we want to create is for the JS files in the bottom of the Index page. Extend the bundle registration as follows:

public static void RegisterBundles(BundleCollection bundles)
        {
            bundles.Add(new StyleBundle("~/bundles/css")
                .Include("~/Content/Site.css", "~/Content/mystyle.css", "~/Content/evenmorestyle.css"));

            bundles.Add(new ScriptBundle("~/bundles/modernizr")
                .Include("~/Scripts/modernizr-2.5.3.js"));

            bundles.Add(new ScriptBundle("~/bundles/js")
                .Include("~/Scripts/jquery*", "~/Scripts/knockout-*"));
        }

-> We want all files starting with ‘jquery’ and the knockout.js file irrespective of its version number. The Include method will usually be intelligent enough to figure out dependencies: jquery.js must come before jquery.ui is loaded and Include() will ‘know’ that. However, it’s not always perfect. You may run into situations where you must spell out each file in the bundle so that they are loaded in the correct order. So in case your js code does not work as expected this just might be the reason.

A note on virtual paths and relative references:

Generally you can use any path as the virtual path in the ScriptBundle/StyleBundle constructor, they don’t need to ist as real physical paths. However, be careful with stylesheets. As you know you can refer to images from within a stylesheet with relative URLs, e.g.:

background-image: url("images/mybackground.png");

The browser will request the image RELATIVE to where it found the stylesheet, i.e. at /images/mybackground.png. Now check the virtual path we provided in the StyleBundle constructor: “~/bundles/css”. This will translate to the following HTML link tag:

<link rel="stylesheet" href="~/bundles/css" />

When the browser sees that it needs to locate images/…png from this particular stylesheet it will think that the stylesheet came from a file called “css” in the directory “bundles”. Therefore it will make a request for “/bundles/images/…png” and that file will probably not exist. That particular request will not be intercepted and the server returns a 404. So to be on the safe side it’s a good idea to specify a virtual path that better corresponds to the real phsyical path to the css files. Update the following:

bundles.Add(new StyleBundle("~/bundles/css")
                .Include("~/Content/Site.css", "~/Content/mystyle.css", "~/Content/evenmorestyle.css"));

to

bundles.Add(new StyleBundle("~/content/css")
                .Include("~/Content/Site.css", "~/Content/mystyle.css", "~/Content/evenmorestyle.css"));

Now the browser will think the css file came from a file called “css” in the directory “content” and sends a request for “/content/images/…png” which will be the correct solution. You can still have e.g. ~/content/styles or ~/content/ohmygod as the virtual path but the folder name, i.e. ‘content’ in this case should correspond to where the css files are located.

Following the same reasoning if you know that any of your JS files makes a reference to other files using relative paths then you will need to update the virtual paths of the ScriptBundle object as well: “~/scripts/js”.

Let’s carry on: how do we render the script tags of those bundles?

This is very easy as Razor has built-in methods to achieve this:

@Styles.Render("~/content/css")
@Styles.Render("~/bundles/js")

MVC will take care of rendering the script tags. Another, more fine-grained way of doing this is to use the following in the HTML code:

<link href="@BundleTable.Bundles.ResolveBundleUrl("~/bundles/css")" rel="stylesheet" type="text/css" />

The optimisation framework will also add more information to the URL: a query string value that will avoid caching old versions of the bundle. If you update one of the files in the bundle then a new query string will be generated and the browser will have to fetch the new updated version instead of using the cached one.

Before we update our code we can test the following: start the application and extend the URL to send a request for one of the bundled resources using its virtual path, e.g. http://localhost:xxxx/bundles/js. You should see something like this:

Request for a bundled resource

It’s looking very much like minified JavaScript. I cannot be sure just by looking at the contents that it includes everything from the bundled resources but believe me it does.

So now we’re ready to update _Layout.cshtml:

<!DOCTYPE html>
<html lang="en">
    <head>        
        <title>@ViewBag.Title - My ASP.NET MVC Application</title>
        @Styles.Render("~/content/css")
        @Scripts.Render("~/bundles/modernizr")
    </head>
    <body>
        <div>
            @RenderBody()
        </div>
        @Scripts.Render("~/bundles/js")
    </body>
</html>

Run the application again, check the generated HTML code and you’ll see…:

HTML source with debug mode

…not quite what you expected, right? The bundled files still appear as individual files, so what’s going on? It turns out that the debug/release settings of your application will make bundling/minification behave differently. In debug mode bundling is ignored for easier debugging and we ran the application exactly in that mode. How do we know that? Check web.config and you’ll see the following tag under system.web:

<compilation debug="true" targetFramework="4.5" />

Change debug to false and re-run the application. The source code should look similar to this:

Html source without debug

That looks better. The files have been bundled and the correct link and script tags have been created. Also note the query string mentioned before attached to the href and src values.

As a result we have 1 link tag instead of 3 and 2 script tags instead of 7 we started off with.

There is an alternative way to force bundling even if debug = true in web config. Go to BundleConfig.RegisterBundles and add the following code after the bundles.Add calls:

BundleTable.EnableOptimizations = true;

This will override debug = true in the web.config file and emit single script and link tags.

As mentioned before bundling also performs minification: you can verify this by requesting the bundled resource in the URL, i.e. by requesting http://localhost:xxxx/bundles/modernizr?v=jmdBhqkI3eMaPZJduAyIYBj7MpXrGd2ZqmHAOSNeYcg1
You should see a jumbled version of the original modernizr.js file.

Have we achieved any improvement in the response time?

Run the same test with the web developer tool as we performed in the beginning. You should definitely get a lower response time. Also check the number of resources and the total size of the resources that the browser needs to download. The number of requests is reduced as we have fewer resources to download and the total size of the resources is greatly reduced due to the minification process.

We achieved all this by very little work – at least compared to how you would have done all this manually.

Some of you may have CoffeeScript files in your web project. You probably would like to bundle and minify them as well. I’ll demonstrate in the next blog post exactly how to do that.

View the list of MVC and Web API related posts here.

ultimatemindsettoday

A great WordPress.com site

Elliot Balynn's Blog

A directory of wonderful thoughts

HarsH ReaLiTy

A Good Blog is Hard to Find

Softwarearchitektur in der Praxis

Wissenswertes zu Webentwicklung, Domain-Driven Design und Microservices

Technology Talks

on Microsoft technologies, Web, Android and others

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

WEB APPLICATION DEVELOPMENT TUTORIALS WITH OPEN-SOURCE PROJECTS

Once Upon a Camayoc

Bite-size insight on Cyber Security for the not too technical.

Guru N Guns's

OneSolution To dOTnET.

Johnny Zraiby

Measuring programming progress by lines of code is like measuring aircraft building progress by weight.

%d bloggers like this: