Professional Programmer Notes

or just call this my soapbox

Improve page load performance in your ASP.NET MVC site

with 3 comments

As ASP.NET MVC become more popular in the enterprise and behind high-traffic commercial web sites (like Dimecasts.net), developers will look for ways to increase performance.

One well known way to increase performance of any website is to combine files to minimize HTTP requests (see Yahoo! Performance Rules). The reason is that most modern browsers use two threads to load a given page and all of its assets (css, js, images, etc.). This post will give a quick walk-through of an ASP.NET MVC implementation of a CSS consolidator.

In typical MVC fashion, I will give a quick breakdown of the model, the view, and the controller.

Let’s start with the controller.

My controller has one need: it provides a pretty url for me to point my tag to retrieve passed-in stylesheets. I implemented a controller called StaticController and gave it an action called CSS. Here is a snapshot of the action:

        public ActionResult CSS(string id)
        {
            if (String.IsNullOrEmpty(id)) return View();

            string[] filenames = id.Split(',');
            string path = HttpContext.Request.ServerVariables["APPL_PHYSICAL_PATH"];
            path = String.Concat(path, @"Content\"); // path to css files

            string cssData = Models.StaticCSS.GetContent(filenames, path);
            ViewData["css"] = cssData;

            return View();
        }

Quick explanation of the above code:
The action uses the default MVC routes (for simplicity). So, the id parameter is used to pass a comma-delimited string of stylesheet names. The path to the Content directory (the default folder for stylesheets) is derived, combined with each file name in the id parameter and passed to the model.

Let’s see how our model works.

The model is named StaticCSS and here are the methods:

        public static string GetContent(string[] filenames, string cssDir)
        {
            StringBuilder sb = new StringBuilder();
            if (filenames.Length == 0 || String.IsNullOrEmpty(cssDir))
return String.Empty;

            foreach (string f in filenames)
            {
                string filepath = (f.EndsWith(".css")) ?
String.Concat(cssDir, f) : String.Concat(cssDir, f, ".css");
                sb.Append(@"/* Start CSS file */");
                sb.Append(Environment.NewLine);
                sb.Append(GetFileContents(filepath));
                sb.Append(@"/* End CSS file */");
                sb.Append(Environment.NewLine);
            }

            return sb.ToString();
        }

        public static string GetFileContents(string file)
        {
            try
            {
                using (TextReader textReader = new StreamReader(file))
                {
                    return textReader.ReadToEnd();
                }
            }
            catch(FileNotFoundException)
            {

            }

            return String.Empty;
        }

Quick explanation:
The public static method, GetContent loops through a list of files, combines their contents into a string, and return the string. The GetFileContents method opens the file and returns the contents.

If we look back at the remaining code in our action:

            ViewData["css"] = cssData;

            return View();

We are adding the css file contents to the ViewData hash and calling the view.

Here are the important parts of the view:

<%
    Response.ContentType = "text/css";
    Response.ContentEncoding = Encoding.UTF8;
    Response.Write( Server.HtmlDecode(ViewData["css"].ToString()) );
%>

We are setting the content-type to “text/css”, which is important in standard-compliant browsers (not IE). We’re setting the encoding dumping the content to the page. These three lines essentially turn the view into a standard css file.

Finally, to use this code I would simply add the following line to any view requiring css files:

<link href="/Static/CSS/cssfile1,cssfile2,cssfile3" rel="stylesheet" />

This would download cssfile1, cssfile2, and cssfile3 in one HTTP request instead of three.

This is a simplistic approach that doesn’t care (yet) about the amount of memory used to load very large css files. But, if you choose to follow this model, I would love to hear how it performs for you.

Advertisements

Written by curtismitchell

March 17, 2009 at 10:42 pm

Posted in .net, CSharp

Tagged with , , ,

3 Responses

Subscribe to comments with RSS.

  1. What’s to stop someone from linking to this:

    Seems like a pretty serious security hole. A simple wrap of “Path.GetFilename()” around the initial entry should fix that.

    Otherwise, it’s an interesting idea. I’ve recently broken up some JS files to make them more maintainable and better organized. This would be a nifty way to stitch them back to together for delivery. Implement an in-line minify-er to maximum optimization.

    Peter Lanoie

    August 5, 2010 at 8:22 pm

  2. Hmmm… previous comment didn’t like my HTML, let’s try this:

    What’s to stop someone from browsing to this:

    /Static/CSS/..\web.config

    and downloading normally non-deliverable files?

    Peter Lanoie

    August 5, 2010 at 8:24 pm

  3. Hi Peter,

    That is a great point about security and honestly, I hadn’t really considered that.

    However, I think the GetContent method prevents that by limiting the content to .css files. If someone added web.config, GetContent would look for web.config.css in my Content directory. It would cause a FileNotFound exception, which is basically ignored.

    Thanks for bringing that to my attention.

    Curt

    November 9, 2010 at 5:41 pm


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: