Click here to Skip to main content
Click here to Skip to main content

Faster page rendering by downloading JS/CSS before server generates full page

, 15 Aug 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
Let browser download Javascripts, CSS while server is still performing expensive operation and generating the page output

Introduction

When a dynamic page is executing on the server, browser is doing nothing but waiting for the html to come from the server. If your server-side code takes 5 seconds to perform database operations on the server, then for that 5 seconds, user is staring at a blank white screen. Why not take this opportunity to download the Javascript and CSS on the browser simultaneously? By the time server finishes doing its work, server will just send the dynamic content and browser will be able to render it right away. This optimization technique can improve the performance of any dynamic page that takes some time to finish its job on the server, and has some js and css to download.

Here's an example of a typical page, where the JS/CSS are loaded only after the page is delivered to the browser:

It takes 7 seconds to render the content.

Here's the optimized version, where the JS/CSS are loaded by the browser, while server is still producing the page content:

This one renders at 6 sec, a whopping 1 sec saving! The more JS, CSS and slower dynamic page you have, the more improvement you get.

But, this is easy to do!

This is easy to do using technologies like PHP, NodeJS, old ASP, where you directly write the output from the code and you can flush the response anytime. For example, a PHP solution would be, as Steve Souders has shown, like this:

<?php
// Flush any currently open buffers.
while (ob_get_level() > 0) {
    ob_end_flush();
}
ob_start();
?>
[a bunch of HTML...]
<!-- 0001020304050607080[2K worth of padding]... -->
<?php
ob_flush();
flush();
require_once('trends.inc'); // contains the slow query
?> 

But for ASP.NET WebForms or ASP.NET MVC, especially using Razor view engine, this is complicated. You cannot just call Response.Flush anywhere. For ASP.NET WebForms, it will mess up the view state, postback event handlers etc. For ASP.NET MVC using Razor, it just does not work.

There are some clever ways to do it in ASP.NET MVC. For example, using Partial Views, which holds the content that you want to send immediately, like the <head> section full of js, css. An example would be:

public ActionResult FlushDemo()
{
       PartialView("Head").ExecuteResult(ControllerContext);
       Response.Flush();

       // do other work
       return PartialView("RemainderOfPage");
}  

But this means, you cannot use the Layouts to hold your <head> section. You have to stop using any Layout solution. Moreover, your controller now has to do the plumbing work for splitting the view and sending a part of the view early. It is now polluted with some 'viewy' type code.

It would be good to do it from the View, where you can define which part of the View should go first and which one should go after the server side work is done. Moreover, an ideal solution should work perfectly fine with layouts.

A solution for Razor

First, we want to make the Controller do only 'Controller' stuff and not worry about views, partial views, response flush etc. Here's how to do it:

public ActionResult Index()
{
    Thread.Sleep(5000); // Do something expensive here
    var model = new HomeModel()
    {
        Text = "Hello"
    };
    
    return View(model);
}

public ActionResult FastIndex()
{
    var asyncModel = new AsyncModel<HomeModel>(new HomeModel(),
        model =>
        {
            Thread.Sleep(5000); // Do something expensive here
            model.Text = "Hello";
        });

    return View(asyncModel);
} 

The first Index()shows you how you would typically write a controller code. It is the slow version. The FastIndex() is the faster version. The idea is to defer execution of the code that generates the model, until the view rendering starts.

Now here's the slow view:

@{
    ViewBag.Title = "Home";
}

<p>From slow index: @Model.Text</p> 

Here's the faster version of the same view:

@{
    ViewBag.Title = "Home";
}

@section AsyncBody {    
    @{ViewData.Model = Model.Execute();}
    <p>From faster index: @Model.Text</p>
}

AsyncBody section contains the View stuff that is rendered after the expensive code in the controller is run. Inside that section, first it calls Model.Execute(), which calls the Execute() function of AsyncModel. It then fires the delegate that runs the code defined in the controller, inside FastIndex function.

Now comes the layout part. The layout takes care of rendering the <head> part immediately, and then renders the AsyncBody section on the view.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>@ViewBag.Title</title>
    <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
    <script src="@Url.Content("~/Scripts/jquery-1.7.1.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.validate.min.js")"></script>
    ...
    ...   
</head>

<body>
    <p>Before Body</p>
    @if (IsSectionDefined("AsyncBody")) {
        var sb = ((StringWriter)ViewContext.Writer).GetStringBuilder();
        Response.Write(sb);
        Response.Flush();
        sb.Length = 0; 
        @RenderSection("AsyncBody");
    }
    else {
        @RenderBody();    
    }    
    <p>After Body</p>
</body>
</html> 

Here, it grabs the Razor internal buffer that holds the generated content and flushes it on the response. Then it invokes the @RenderSection("AsyncBody"), which then invokes Model.Execute(), which in turn invokes the controller expensive code.

The AsyncModel class is doing nothing really:

public class AsyncModel<T>
{
    private Action<T> ControllerCode;
    public T RealModel;
    public AsyncModel(T realModel, Action<T> controllerCode)
    {
        this.ControllerCode = controllerCode;
        this.RealModel = realModel;
    }
    public T Execute()
    {
        this.ControllerCode(this.RealModel);
        return this.RealModel;
    }
} 

That's it!

How it looks

When you hit the page, you immediately get some output and the js, css starts downloading, while the server is doing its job:

When server has finished doing its job, the rest of the content renders:

You can see the great saving in js, css downloading time achieved here.

 

 

Conclusion

Here you have a solution for ASP.NET MVC, especially for Razor to render part of the page immediately, while server is doing expensive work. For regular ASP.NET WebForm, it is a lot more complicated, something we will explore later on. Since ASP.NET MVC has no Control object model, no Page state, no ViewState, it is a lot easier to do in MVC, than in WebForms. By implementing this solution, you can make almost any ASP.NET MVC Razor page load and render significantly faster.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Omar Al Zabir
Architect BT, UK (ex British Telecom)
United Kingdom United Kingdom

Comments and Discussions

 
GeneralGreat article PinmemberRickyJiao22-Jun-14 16:22 
GeneralMy vote of 5 PinmemberHumayun Kabir Mamun8-May-14 19:36 
GeneralIngenious PinmemberOmar Gameel Salem6-May-14 14:28 
QuestionSo this negates the need to put scripts at the bottom? PinmemberSteven Quick30-Apr-14 15:48 
QuestionNamespace for AsyncModel PinmemberKuben Pillay15-Apr-14 17:46 
AnswerRe: Namespace for AsyncModel PinmemberOmar Al Zabir16-Apr-14 0:20 
QuestionThank you for come samples for the different languages PinmemberVMAtm14-Apr-14 22:09 
Questionachieve same in simple asp.net application PinmemberAbhishek Ranjan Srivastava14-Apr-14 0:12 
AnswerRe: achieve same in simple asp.net application Pinprotectorthatraja14-May-14 21:46 
QuestionThis looks really Interesting. Pinmemberdedlok11-Apr-14 0:54 
AnswerRe: This looks really Interesting. PinmemberOmar Al Zabir12-Apr-14 6:53 
QuestionWebForm PinmemberHoangitk7-Apr-14 21:13 
AnswerRe: WebForm Pinprotectorthatraja14-May-14 21:44 
GeneralRe: WebForm PinmemberHoangitk16-May-14 3:25 
QuestionCaching PinmemberNeverJustHere7-Apr-14 3:27 
AnswerRe: Caching PinmemberOmar Al Zabir12-Apr-14 6:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.141015.1 | Last Updated 15 Aug 2014
Article Copyright 2014 by Omar Al Zabir
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid