[Note: The code for this project is available on GitHub.]
Imagine you’re building an ASP.NET MVC website which has some performance problems. I’m sure this would never actually happen to you, but imagine you’re facing this problem just for the sake of exploring the possibilities. :-)
First a disclaimer / warning. I’m going to show you how to make your site feel faster without speeding it up. If the users feel that it’s fast, then it’s fast enough. However, it might be better to try to just make it faster in the first place. This article assumes you have either tried or ruled out things like increasing the DB performance by adding the proper indexes, caching data where feasible, optimizing queries, etc. OK, assuming you’ve done as much as you can for pure performance, let’s get to improving the experience for the users.
Consider a basic website that shows data from several sources. In the sample case, it’s kind of an iGoogle landing page with different items: TODO, news, etc. In the screenshot below you can see there are several sets of information shown (my details, news, and most popular items).
It turns out that my details and the main page are actually pretty fast. The news and popular items are not. Yet, we are computing them all at once in the MVC action. Thus the whole page feels sluggish and slow. Here is the underlying controller action method.
This is a perfectly reasonable action method. But after doing some profiling we see that we have the following performance footprint:
10ms repository.GetUserItems() 500ms repository.GetNews() 2000ms repository.GetPopular() 2510ms TOTAL
I don’t know about you, but 2.5 seconds seems like an unacceptable page load time. Don’t think so? Check out these amazing facts:
Latency is Everywhere and it Costs You Sales – How to Crush it
Latency matters. Amazon found every 100ms of latency cost them 1% in sales. Google found an extra .5 seconds in search page generation time dropped traffic by 20%.
So a simple solution would be to break the loading of the slower sections (news & popular in our case) apart from the faster sections in the page. We can load the fast data immediately using direct model to razor processing and push the loading of the slow parts to an asynchronous operation using AJAX.
That might be a lot of work in general. But with MVC we can employ a few techniques and really make this simple and foolproof.
First, we’re in luck from the start because we are already using partial views (a key step) for our razor code. If you don’t have partial views, they are easy to add. Here is the relevant page section which renders the data synchronously.
MVC will easily serve the PopularControl’s content up over AJAX and same for any other partial view. So let’s change our controller action in the most simple way to allow us to ask for those elements independently. Here’s the new controller code:
It is important to note that we are returning PartialView (not View) for the parts that were previously managed in the view (news & popular).
Assuming the metrics above, the page will now load in 10ms. That’s vastly better than 2,500ms. With razor rendering time and other overhead it’s more like 50ms, but still vastly better.
Each section that will be loaded asynchronously now has three changes.
- We have a div with the class partialContents. We use this in jQuery to locate them.
- Each partialContents section has a data-url for the location of the partialview controller action.
- Each partialContents section has a message and image to show an AJAX indicator that we are working on that area. There is no extra effort to show this. It appears by default and will be replaced as the content loads.
So the controller is ready to serve up this partial content. The view is showing a pending action and has a place to put the results. The final step is to just add the smallest amount of jQuery to do the download and update.
That’s it! Now our site loads the slow sections in parallel and asynchronously.
To fully appreciate the difference you should see it for yourself. Check out the live demo I’ve posted here:
Sample: Improve Perceived Performance with Async Partial Views
Slow Version: http://improve-mvc-perf-with-async-views.azurewebsites.net/home
Async Version: http://improve-mvc-perf-with-async-views.azurewebsites.net/async
Be sure to click between them with the nav in the top right. Download the code here:
Finally, here is the original version with the timing measured in FireBug:
Notice the time: 2.6 seconds. Yuck. Here is the timing after the improvement:
Now it’s 50ms! That is pretty awesome. Of course, if you look at the AJAX requests, they still take the same amount of time. But we do get the added benefit of natural parallelism from AJAX:
Now the whole page is ready in around 2 seconds which is in some sense truly faster (not just perceived) than the original.
So to recap, here are the steps we performed.
- Identified the segments of data that are slow.
- Took the original action method and change it to only load the fast content in the controller.
- Moved the CSHTML responsible for rendering each slow section to a partial view if it was not already done.
- Added an action method returning a PartialViewResult for each async section.
- Added a partialContent pending section with loading message and data-url to parts of the original page which are going to be loaded asynchronously.
- Use jQuery to find and call .load() for each section.
That’s it. Hopefully you will find this a useful technique for your slower pages.