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

Architecting Cascading Style Sheets (CSS)

, 26 Aug 2011
Rate this:
Please Sign up or sign in to vote.
Architecting Cascading Style Sheets (CSS)

There is one big problem with CSS, and that is its lack of ‘structure’. When you have a 20-page project, fine – dump everything in one file. When you have hundreds of pages, across half a dozen different design agencies, things start to become very messy.

And I hate mess.

A client of mine is in this situation. Over three years, they have had 6 different designers, each with different visual styles – but, more importantly for me – each with different CSS requirements and implementations.

The impact of this has been bothering me ever since I began, but I’ve always had more ‘architectural’ problems to deal with. However, I decided enough is enough when I realized our total CSS payload was 1Mb, and our biggest CSS file consisted of over 5,000 lines.

Architecting CSS

There are a few things I want from my CSS:

  • I’m mostly a C# developer these days, so I like the object-oriented thing – I get it, it’s tidy, it’s re-usable.
  • The ‘cascading’ part of CSS is wonderful, but it is a double-edged sword. When some designer submits a stylesheet with a tag like “.main h2”, then it will inevitably affect portions of the site they weren’t even involved in.
  • Problems with CSS only manifest themselves when you actually view it – in other words, they are runtime errors. And when you have hundreds of pages in the site, it is often the users that report a styling problem, not our testers.

Close, but not close enough

Unfortunately, I can’t claim to fix any of these problems. I admire the ambition of projects like .less (www.dotlesscss.org/) but something about these didn’t really feel right to me. I guess it's because there is no mainstream support (mainstream support is very important to me as we have a constant stream of new developers coming through – standards are my friend).

I’ve also read a lot of people’s opinions on CSS – how we need to move away from semantics, how we need to move towards semantics, etc. but again, I need something that has plenty of community support on the internet, and that my new developers will be able to hit the ground running with.

A Brilliant Compromise

So, in the interim, while I search for the perfect solution, I’ve come with with a pretty-damn-good solution. It consists of a single user control. You might call it WonderControl, but I call it simply CssContainer.ascx:

ASCX

<asp:PlaceHolder runat="server" ID="PHStyle" />

Pretty simple huh? One line.

Code-behind

public partial class CssContainer : BaseUserControl
    {
        [
        PersistenceMode(PersistenceMode.InnerProperty),
        TemplateContainer(typeof(CssContainer)),
        TemplateInstance(TemplateInstance.Single),
        ]
        public ITemplate Style { get; set; }

        /// <span class="code-SummaryComment"><summary>
</span>        /// Init
        /// <span class="code-SummaryComment"></summary>
</span>        /// <span class="code-SummaryComment"><param name="e"></param>
</span>        protected override void OnInit(EventArgs e)
        {
            // Instantiate any CSS in our template
            this.EnableViewState = false;
            base.OnInit(e);
            if (Style != null) Style.InstantiateIn(PHStyle);
        }

        /// <span class="code-SummaryComment"><summary>
</span>        /// Pre render
        /// <span class="code-SummaryComment"></summary>
</span>        /// <span class="code-SummaryComment"><param name="e"></param>
</span>        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);

            // If, for some reason, the control is called twice, we ignore
            if (!HasRegisteredOnThisRequest())
            {
                this.Visible = true;
                this.RegisterStyles();
            }
            else
            {
                this.Visible = false;
            }
        }

        /// <span class="code-SummaryComment"><summary>
</span>        /// This control is designed to render styles only 
        /// once per request - duplicate styles are a waste of resources
        /// <span class="code-SummaryComment"></summary>
</span>        /// <span class="code-SummaryComment"><returns></returns>
</span>        private bool HasRegisteredOnThisRequest()
        {

            // Has this control been rendered before?
            Control parent = this.Parent;
            while (true)
            {
                if (parent == null) break;
                if (parent.GetType().IsSubclassOf(typeof(BaseUserControl)) || 
				parent.GetType().IsSubclassOf(typeof(PageBase)))
                {
                    break;
                }
                parent = parent.Parent;
            }
            if (parent == null)
            {
                // I'll throw an exception here, but really you could just render 
                // it anyway - it would just result in multiple renderings per page
                throw new Exception("CssContainer may only be used on classes 
				inheriting from BaseUserControl or PageBase");
            }

            // The count is kept in the PageBase so that we may retain it per-request, 
            // but share across all instances of this CssContainer control
            if (!this.Page.GetType().IsSubclassOf(typeof(PageBase)))
            {
                // I'll throw an exception here, but really you could just 
                // render it anyway - it would just result in multiple renderings per page
                throw new Exception("CssContainer may only be used on pages 
					inheriting from PageBase");
            }

            // This control is probably specified multiple times on any one page. 
            // Here, we record that its' been done already or not
            if (pageBase.CssContainerCount.ContainsKey(parent.GetType())) return true;
            pageBase.CssContainerCount[parent.GetType()] = 1;
            return false;
        }

        /// <span class="code-SummaryComment"><summary>
</span>        /// Register the script contents, if any, to the page
        /// <span class="code-SummaryComment"></summary>
</span>        private void RegisterStyles()
        {
            // Pull template string OUT of the template we rendered in initially
            var sb = new StringBuilder();
            var tw = new StringWriter(sb);
            var writer = new HtmlTextWriter(tw);
            this.PHStyle.RenderControl(writer);
            writer.Close();
            tw.Close();
            var css = sb.ToString();
            if (string.IsNullOrWhiteSpace(css)) return;

            // User controls can be in multiple folder paths, 
            // so we must normalize any URLs
            css = css.Replace("~/", WebHelpers.GetFullUrlForPage(""));

            // Compress CSS etc
            css = Yahoo.Yui.Compressor.CssCompressor.Compress(css);

            // Render to page
            this.PHStyle.Visible = false;
            this.Controls.Add(new LiteralControl("<style type=\"text/css\">" + 
			css + "</style>"));
        }
    }

Okay, a little flasher, but really very simple. I have tried to comment the code intuitively so you can follow it, but the general idea is:

  1. The user of the control specifies regular CSS in a template (called Style)
  2. We render that content into the PHStyle placeholder, just as you would a normal template
  3. We later get that CSS back out of the template, and render to the page between regular <style/> tags

So, the end usage is something like this:

<wc:Css runat="server">
<Style>
.column-con <span class="code-none">{
    overflow<span class="code-none">: hidden<span class="code-none">;
<span class="code-none">}
.column-con .column <span class="code-none">{
    background-color<span class="code-none">: #fff<span class="code-none">;
<span class="code-none">}
</Style>
</</span></span></span>wc:Css></span></span></span></span></span>

The intention is that you drop this CSS directly into the page/usercontrol that is using it.

Why This Is Bad…

Yip, I understand what the problems are with this:

  1. It is inline, meaning page requests are bigger
  2. It can’t be cached on the browser
  3. It can only be re-used as far as the page or usercontrol it is sitting on

But the answer to all of these is simple: if the pain of breaking these rules, for this set of styles, is too great, then just put them back in your regular external CSS style sheet.

This control is not intended to replace your external sheets, but rather complement them.

So, Why It’s Good…

There’s only one reason: it’s tidy. Where we have an obscure user control or page whose styles depart significantly from our usual styles, this is a brilliant way of isolating the styles without cluttering the main style sheet.

And because it is rendered in code (as opposed to using direct <style/> tags), I know that I have a lot of control over what I might want to do with this in the future. For example, I could:

  1. fake an external ‘file’ reference using a handler, thereby allowing the resulting request to be cached in the browser
  2. minimize the CSS (actually, I’m already doing this in the example above)
  3. perform replacements such as variable names – just like they do at www.dotlesscss.org/
  4. I can easily find the styles which apply to my HTML, by just browsing to the top of the UserControl – no more ‘Search Entire Project’ to track down a CSS source (update: Resharper 6 has a fairly good implementation of ‘Go To Definition’ for CSS)
  5. etc. etc. etc.

But until I know how to handle my styles, this solution allows me to contain and manage them far better than regular ‘client-side’ CSS files and embedded <style/> tags allow.

What About Re-usable External Style Sheets?

Previously, where we had isolated CSS, we would create separate CSS files and then pull them into the request ‘on-demand’. Technically, this worked just as well as my solution above – in fact, it was better in that the resulting file would be cached on the client browser and thereby improve overall visit speed.

But this can result in dozens (or much more) of CSS files cluttering up your project, confusing your developers, slowing your project load time, etc. And besides, as I said above, I know that later, when I’m less busy, I’ll be able to create ‘external’ references using ASHX handlers.

But Embedded Styles are Terrible!

In theory, yes – but in practice, definitely not. One of the worst things a developer can do is get caught up in ‘best practice’. As long as you understand the reasoning behind the rules, you’re able to break them when you find the reasoning doesn’t apply to your current project.

Besides, go do a ‘view source’ on any of these home pages – you’ll see a lot of embedded styles:

See? I’m in good company.

Conclusion

This is not the most technically-brilliant piece of work I’ve done, but I have to say it’s one of the most exciting. I love being able to recklessly add styles exactly how I like with little regard for structure – content in the knowledge that future-Ben will be able to easily tidy it up one day.

I strongly urge other developers to consider this type of structure for any medium-large projects they are undertaking.


License

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

About the Author

Ben Liebert
Architect BlackBall Software
New Zealand New Zealand
No Biography provided
Follow on   Twitter

Comments and Discussions

 
QuestionThis is all good when it comes to a single themed site PinmemberDaniel Gidman29-Aug-11 3:16 
AnswerRe: This is all good when it comes to a single themed site PinmemberBen Liebert29-Aug-11 9:15 

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
Web03 | 2.8.140721.1 | Last Updated 26 Aug 2011
Article Copyright 2011 by Ben Liebert
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid