One of the first things a developer should do when inspecting the performance of their ASP.NET website is to understand how the static assets of the site are served and if they are correctly being cached by the visitor's browser. On this post let's review a solution that works well, is built into the ASP.NET framework and helps you reduce costs and improve the performance of your site.
How caching works
Before getting to solution, it's important to understand how web browsers cache static resources. In order to improve performance and reduce network usage, browsers usually cache JavaScript and external resources a web page may depend on. However, under certain circumstances, modifications on those files won’t be automatically detected by the browser resulting in users seeing stale content.
Approaching the Problem
A common solution to this problem is to add query string parameters to the referenced JavaScript files. Assuming that the suffix has changed, the browser treats the new url as a new resource (even if it’s the same file name), issuing a new request and refreshing the content. Whenever that resource changes, the query string changes and the browser automatically refreshes the resource making users always seeing the most up to date content. Also note how we can use that approach for all linked resources: JavaScript files, CSS, images and even icons.So how to ensure that our pages output references as showed above? In ASP.NET, the solution to that is to use bundles.
Bundling and Minification
According to Microsoft,Bundling and minification are two techniques you can use in ASP.NET 4.5 to improve request load time. Bundling and minification improves load time by reducing the number of requests to the server and reducing the size of requested assets (such as CSS and JavaScript.)Another benefit of bundling is that, by default, the Asp.Net framework comes with a cache-prevention feature
Bundles set the HTTP Expires Header one year from when the bundle is created. If you navigate to a previously viewed page, Fiddler shows IE does not make a conditional request for the bundle, that is, there are no HTTP GET requests from IE for the bundles and no HTTP 304 responses from the server. You can force IE to make a conditional request for each bundle with the F5 key (resulting in a HTTP 304 response for each bundle). You can force a full refresh by using ^F5 (resulting in a HTTP 200 response for each bundle.)
Bundling in practice
Before reviewing the actual solution, let’s compare a page serving bundled content and a page showing unbundled content and understand how the browser behaves on each of them.Reviewing a request with bundled content
Let's take a look at a request using bundles to understand this a little better:From the above, note that:
- in yellow, the auto-added suffix. We'll see later why this is there and how to do it ourselves;
- in red, a confirmation that that resource was cached by the browser and loaded locally, not issuing any request to the server;
- the time column on the right, shows the time spent by the browser to load that resource. For most files it was 0ms (or, from cache);
Serving unbundled content
Now if you take a look at a request to a page that has unbundled content, you'll see that the resources are also being cached (column Size). However, individual requests are still being issued for each file referenced causing two problems:- In HTTP 1.1, there’s a virtual limit of max 6 concurrent requests per domain. On that case, after the 6th request is issued, others will be waiting until a new slot is available
- Because after each deploy file names won’t change, the browser will reuse the cached version event if modified (because by seeing the same file name, it understands the resource didn’t change). Our problem is that updates on those files won’t be automatically refreshed making users see stale content.
Bundling in ASP.NET
{
// ...
BundleConfig.RegisterBundles(BundleTable.Bundles);
// ...
}
{
bundles.Add(new StyleBundle("~/Content/css")
.Include("~/Content/images/icon-fonts/entypo/entypo.css")
.Include("~/Content/site.css"));
}
Conclusion
References
See Also
- Microservices in ASP.NET
- My journey to 1 million articles read
- Creating ASP.NET Core websites with Docker
- Send emails from ASP.NET Core websites using SendGrid and Azure
- Distributed caching in ASP.NET Core using Redis, MongoDB and Docker
- Configuration in .NET Core console applications
- Hosting NuGet packages on GitHub
- Package Management in .NET Core
- Importing CSVs with .NET Core and C#
- Exporting a CSV generated in-memory in ASP.NET with C#
- Building and Running ASP.NET Core apps on Linux
- A simple chat room in Vue.Js