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

Architecting Cascading Style Sheets (CSS)

By , 26 Aug 2011
 

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; }

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

        /// <summary>
        /// Pre render
        /// </summary>
        /// <param name="e"></param>
        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;
            }
        }

        /// <summary>
        /// This control is designed to render styles only 
        /// once per request - duplicate styles are a waste of resources
        /// </summary>
        /// <returns></returns>
        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;
        }

        /// <summary>
        /// Register the script contents, if any, to the page
        /// </summary>
        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 {
    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>wc:Css></

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
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 4memberBrianBissell1 Sep '11 - 10:19 
Interesting thought, well written and explained.
 
I am not inclined to agree, but I certainly am glad you shared your technique, quite interesting!
GeneralToo muchmemberJakub Januszkiewicz31 Aug '11 - 11:33 
I feel that your solution, while potentially powerful, unnecessarily drifts too far from just using regular CSS files (or LESS, or SASS, or ...).
 
When I need to style some part (page, user control, whatever) of the web app in a different way than the rest, I just assign a distinctive CSS class to the container element (e.g. to the control's root container - and I often name the class after the control name). Then I put a bunch of CSS rules with selectors prefixed with that class to the main CSS file (or add them in a separate CSS file - this allows to have per-control CSS files, so finding relevant rules is not a problem). Those rules are stronger than the default rules for the web app, so they take precedence.
 
I find my solution cleaner and more straightforward (it is actually hardly a "solution" - just a normal way of using CSS). Plus it can be used in any technology, not only in ASP.NET web forms.
 
Care to comment?
GeneralRe: Too muchmemberBen Liebert31 Aug '11 - 11:44 
Yeah, I think that's a very fair point and in fact its how we usually do it too. The reason I am drifting away from this though is that you either end up with heaps of CSS files, or you end up with a huge main style sheet (the majority of which is never required by an average visitor). Note that my solution also requires you to write fairly 'domain specific' class names, as we don't know which combination of user controls might be on a single page at any given time - so the classes mustn't collide.
 
Thanks for your feedback.
GeneralRe: Too muchmemberJakub Januszkiewicz2 Sep '11 - 11:41 
I think it is possible to get the best of both worlds - have a clear CSS files structure in design/development time and a good runtime performance (smaller/fewer HTTP requests).
 
I am actually toying with an idea for one of our projects:
- there should be a few CSS files which are always needed: a CSS reset file and a "main" CSS file defining overall page layout/style,
- each control which requires individual styling should have a root element with a CSS class named after the control name,
- each such control should have an individual CSS file with all its CSS rules,
- there should be a single instance of a "CSS manager" control (e.g. in the root master page) - it can be Telerik's RadStyleSheetManager (we're using Telerik's controls), but an equivalent can be coded easily,
- each control which comes with its own CSS file should register that file with the CSS manager (e.g. during PreRender),
- the CSS files are placed outside of App_Themes so that they are not added automatically,
- no CSS files are included in the <head> markup,
- the CSS manager adds a single <link> element pointing to a HTTP handler which serves consolidated and minified content of the CSS files registered during the current request.
 
I'm not yet entirely happy with the design (I've also omitted a few things in the description above, e.g. allowing for multiple themes). Now that I think about it, it is in many ways similar to what you are describing. A potential strength is that it makes creating a controls library easier - the CSS files can become embedded resources. I also like the idea of having all CSS in actual physical CSS files in the project more than having the styles hidden in markup here and there.
 
By the way, I think Telerik is doing something similar with their embedded stylesheets and scripts. I need to check it out in Reflector.
QuestionThis is all good when it comes to a single themed sitememberDaniel Gidman29 Aug '11 - 3:16 
However, as soon as you cross over into a multi-tenant site it starts to break down very fast. Largest reason to keep CSS and some page specific JavaScript files to a separate App_Themes folder is to support multiple looks and feels in one central structure rather than hunting through code files.
AnswerRe: This is all good when it comes to a single themed sitememberBen Liebert29 Aug '11 - 9:15 
Hi, thanks for the feedback. Actually I have found it is more sustainable this way. A stripped back the example, but in production we have a enumeration which lets us specify styles on a per-theme basis. We also use Resharper 6 which means we can Ctl-Click any CSS name and go straight to its definition - it doesn't matter if it is in external files or in the class itself - Resharper will find it.
 
Remember, over-arching styles such as colours, fonts, etc should still be kept in external style sheets because they are used across multiple pages/controls. This tool is just for control/page-specific styles, which are usually layout-related (well, in my experience).

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

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130523.1 | Last Updated 26 Aug 2011
Article Copyright 2011 by Ben Liebert
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid