Faster than Stack Overflow

February 21, 2011

This blog is faster than Stack Overflow.

super fast awesome web app slow crappy web app no one uses

The ASP.NET MVC website, designed and maintained by some of the best developers in the world, has been beat by this home-grown blog engine running on a $10/month shared hosting provider.

I don’t mean to gloat. I just mean my web app pwnz yours!!1

I kid, I kid. As a collective of web performance best practices, Google’s Page Speed is a pretty good measure of how fast a website performs from the client point of view. Naturally, several Page Speed recommendations can come at the cost of server-side performance (or just development overhead) so maxing out your score can quickly become an effort of diminishing returns.

Beyond the 80s

When I built this blog I had a score in the low 80s which isn’t too difficult to achieve on a basic website. Based on nothing more than a gut assertion, I’d say a score in the 80s is the baseline for “good” performance. It means you made an effort. Do the easy and obvious stuff: use proper markup, minify/combine CSS and JavaScript, and avoid many requests, you’ll probably score somewhere in the 80s.

80s

A score in the 90s takes a bit more work, and frankly it may be a better choice to work on server-side performance or useful features depending on your situation. My efforts to score as high as a 94 was for little more than to see if I could get a new high score in this little game of web performance.

Optimization techniques for the ASP.NET website

The following is an assortment of techniques for improving your page speed ranking.

Combine and minify Javascript and CSS

I’m not going go into this in detail. There are many solutions and implementation examples you can find with a few Google searches.  What I do want to do is highlight Justin Etheredge’s SquishIt utility. It’s small, simple, slick and easy.

For instance, take all the CSS files on your site (or page) and add them to a bundle like so:

<%= Bundle.Css()
        .Add("~/css/reset.css")
        .Add("~/css/text.css")
        .Add("~/css/960.css")
        .Render("~/css/combined_#.css")
%>

And out comes something like this:

<link rel="stylesheet" type="text/css"  href="/css/combined_55A2DED9A14F8B269A584B0E56382BE4.css" />

One CSS file, whitespace minified. Would you like fries with that? If you’re wondering what the ugly mess of characters in the name is for – that’s the hash of the file contents. It’s necessary so when you change one of the source files, SquishIt knows it needs to create a new bundle (and your browser knows not to use the cached old version it may have).

Check out SquishIt on Justin’s blog for more details.

Optimize Images by applying lossless compression

This was actually new to me. There are several tools available that can perform lossless compression on JPEG and PNG files with no effect on image quality. Google recommends a few tools here.

I downloaded all my Windows Live Writer generated PNG files and ran them through PUNIG, a .NET GUI frontend for OptiPNG. A minute later I had 5MB of images reduced to 4.5MB or so. That’s not amazing, and frankly it’s not worth it to me and this blog, but things like this can add up in bandwidth costs. 10% reduced payload, for free? Something to consider.

Leverage browser caching

This can be done programmatically, but if you have access to IIS 7’s admin console (or want to create a web.config) you can easily turn this on for certain directories.

Go to and open the HTTP Response Headers feature

http response headers

Click “Set Common Headers…” in the Actions pane

set common http response headers

Check “Expire Web content” as desired.

Remove query strings from static resources

Resources with a “?” in the URL are not cached by some proxy caching servers, so if you serve up files like I do on this blog (eg. /image.axd?picture=image_84.png) they may not be cacheable by some proxy servers.

This is something I’m tempted to do because it’s not too hard – even if I wanted to retain the functionality of my image handler, I could just do some URL rewriting to appease Google and any caching server. But you know what. I ain’t here to be perfect. I’m just not certain this is such a big deal. Frankly, I’d say the proxy servers that don’t honor query strings like this are the ones that need fixing. This is perfectly normal and typical HTTP behavior.

Some rules worth avoiding

As I mentioned, not every page speed rule will be worth it.

Minify HTML

Sure, if minifying CSS and Javascript reduces payload, so would minifyingHTML. But on almost any website, the HTML is dynamically generated by server-side code. Is it worth the CPU cycles to send every page output through some squisher just to save a few K in download size? Probably not. Combined with caching of said HTML? Only maybe.

Still, who actually does this? I’ll put this in the “no thanks” category.

Remove unused CSS

This may be the most contradictory rule. Yes, requiring a browser to download a bunch of CSS styles that aren’t even used by the page is a waste. But you also want to serve as few total CSS files as possible so the browser likely has them in cache.

10 pages with 10 small, distinct CSS files, or 10 pages with 1 big CSS file? If you expect your users to hit several pages, you are probably better off forcing them to download 1 big CSS file just once – especially once you consider the development overhead of former option.

Conclusion

Use Google Page Speed to evaluate how well you are taking advantage of client-side performance techniques. While a higher score does imply your site is speedier, there are trade-offs you may have to consider.

At the end of the day, if you have control over your website platform and a make a small effort, you can get a pretty good score. Or you can be CNN.com and just say F&$% it. Really, CNN? A 64 score? 16 Javascript files, no compression, or minification?

String to array, array to string

February 9, 2011

This post is more of a reminder for me than it is some enlightened tutorial. I simply have a mental block when it comes to remembering which methods to use when converting a delimited string into an array and vice versa. Here’s my reminder.

Converting a delimited string into an array

A delimited string can be converted into an array with the String.Split() method. It accepts a character array. You can pass a single character delimiter in single quotes – not double –  and the framework will handle it for you.

string commaString = "separated,by,commas,";
string[] arrayOfStrings = commaString.Split(',');

//Result: [separated][by][commas][]

To prevent empty elements, provide the appropriate StringSplitOptions enumeration. Annoyingly, this overload doesn’t work when providing a single character delimiter. It must be provided as a character array.

string commaString = "separated,by,commas,";
char[] delimiter = new char[] { ',' };
string[] arrayOfStrings = commaString.Split(delimiter, StringSplitOptions.RemoveEmptyEntries);

//Result: [separated][by][commas]

When you have a multi-character delimiter, splitting must be done with a string array thusly.

string bracketString = "[separated][by][brackets]";
string[] arrayOfStrings = bracketString.Split(new string[] { "][" }, StringSplitOptions.None);

//Result: [separated][by][brackets]

Converting an array to a delimited string

An array can be converted into a delimited string with the static String.Join() method. It accepts the string you wish to delimit with and an array of strings.

string[] arrayOfStrings = new string[] { "one", "two", "three" };
string delimitedString = string.Join(",", arrayOfStrings);

//Result: "one,two,three"

You can also join an IEnumerable<string>.

List<string> stringList = new List<string>() {"one", "two", "three" };
string delimitedString = string.Join(", ", stringList);

//Result: "one, two, three"

End.

Tags: string, array, join, split
Categories: C#

Build your own CMS – Iteration 1: the CMSification of the client website

February 5, 2011

This is the fifth post in a series documenting my development and implementation of a content management system (CMS) built on the ASP.NET MVC Platform.

In my previous post, Iteration 1: Data Model, Templates, Content Creation, I gave an overview of how the CMS worked from a data and architecture-centric perspective. This post will pick up where the previous left off, showing you how a client website gets built on this platform.

GlobalBeers.com – now completely content-managed

GlobalBeers is my fictitious client – an online beer retailer with a previously static website. I discussed them and their website in a previous post, Build your own CMS - an overview.

image

All of the site’s old pages have been deleted, and replaced by CMS content pages. To convert the site, I took the following steps:

  1. Create Templates
  2. Create View pages (one for each template)
  3. Create pages in CMS Administration site
  4. Delete all the old static pages

Creating the templates

I started with the individual product pages, so I created a “Product” template, Product.xml, and a master template, PageMaster.xml, which I intend for the product template and others to include. I discussed how this works in my previous post. This is the product template:

<?xml version="1.0" encoding="utf-8" ?>
<template name="Product">
    <contentItems>
        <include template="_Includes\PageMaster.xml" />
        <content id="Title" type="text" isHtml="false"></content>
        <content id="Body" type="textarea" isHtml="true"></content>
        <content id="PrimaryImageUrl" type="text" isHtml="false"></content>
    </contentItems>
</template>

I also created a similar “Default” template, because I realized for now I could get away with using the same template for all of my other pages. It’s identical to the product template, minus the PrimaryImageUrl content element.

Creating the View pages

I created a View page, Product.aspx, for the product pages. It sits in the same master page from the original site and is laid out the exact same way. The only difference is that it inherits a view page of type CmsPageViewModelBase and all CMS content items are rendered via blocks like <%=Html.ContentItem(Model, "Title") %>

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" 
Inherits="System.Web.Mvc.ViewPage<GlobalBeers.Cms.Web.ViewModels.CmsPageViewModelBase>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    <%=Html.ContentItem(Model, "PageTitle") %>
</asp:Content>

<asp:Content ID="Content3" ContentPlaceHolderID="HeaderContent" runat="server">
    <meta name="keywords" content="<%=Html.ContentItem(Model, "MetaKeywords")%>" />
    <meta name="description" content="<%=Html.ContentItem(Model, "MetaDescription")%>" />    
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

    <h2><%=Html.ContentItem(Model, "Title") %></h2>

    <div>
        <img style="float:right; width:150px;" src="<%=Html.ContentItem(Model, "PrimaryImageUrl") %>" />
        
        <%=Html.ContentItem(Model, "Body") %>

        <br /><br />
        
        Quantity: <input type="text" value="1" style="width:20px;" />
        <input type="button" value="Add to cart" />
        
        <div style="clear:both;"></div>
    </div>
</asp:Content>

A Default.aspx page was also created and it is very similar.

Creating the CMS content pages

I went into the CMS Admin site and began creating pages and filling them with content – copying and pasting the content from the original non-CMS website.

 Creating a CMS Page - 1 Creating a CMS Page - 2

Creating a CMS Page - 3

I did the same for all products, the product type pages, the products page, the about page, the contact page and the home page. That leaves me with all 12 pages, all CMS-driven.

CMS Pages

The completed site

The GlobalBeers website is now delightfully simple. Only the 3 highlighted CMS view pages are specific to the CMS implementation. There aren’t even any Controllers (the CMS controller is lives in the GlobalBeers.Cms namespace). Everything else you see here is the same as it was – the code, content, scripts, etc. is just what’s necessary to support the original, CMS-agnostic infrastructure and functionality of the site.

GlobalBeers.WebUI project

But what about the templates?

Okay, you got me. For now, the templates actually live in the GlobalBeers.Cms project. This is part ease, part necessity. Both the GlobalBeers.WebUI (the globalbeers.com website) and the GlobalBeers.Cms.WebUI (the CMS admin site) projects need access to the templates. It would be natural to place them in the GlobalBeers.WebUI project, but then how does the CMS admin site know about them? These web applications are decoupled by design.

GlobalBeers.Cms project

The obvious answer is to put them in a shared resource, such as a database, but then I’d lose all the flexibility of editing template files right through Visual Studio. I’m still contemplating the ideal solution. For now the templates live in the shared project, GlobalBeers.Cms – the heart of the CMS code that both web apps reference.

Conclusion

That’s iteration 1. I’ve built the core CMS architecture and fully converted the fictitious globalbeers.com website to be CMS-driven.

Of course, there’s a long way to go. A few outstanding issues and missing features off the top of my head:

  1. Localization – I have this in the bag, but I’m waiting for the architecture to firm up before implementing. To do.
  2. 90% of a CMS page is one giant HTML blob (the body content item). This is unsustainable.
    1. Content editors aren’t likely to be proficient with HTML, so I’ll need to provide some sort of rich-text editor if I realistically expect editors to produce HTML content. (this could also be a nightmare)
    2. Pages need to be broken up into more granular parts. The Product pages, for instance, list some attributes of the beers. This might best be refactored into individual content items or some sort of content item “list”.
  3. The Menu needs to be CMS-driven.
  4. Need to demonstrate a manual-layout of a CMS content page editing interface as opposed to the dynamically generated ones.
  5. Need to organize content pages in some sort of logical format and allow them to be moved .

I’ll be tackling this and more in future posts.

About Kurt

I'm a senior consultant at Headspring in Austin, TX. My passion is creating web-based applications that are well crafted and solve real problems for real people. Want to know more? Check out my about page.

. @LipGlosserie setting up for Renegade Austin craft fair http://t.co/7X4WBVQb 15 hours ago