MvcCss: bring the convention-based simplicity of MVC to CSS

MvcCss handles CSS files the way MVC handles views: if you name your files conventionally, they will automatically be rendered.

A one-liner introductionfiles

Simply add <%= Html.MvcCss() %> to the <head> of the master page in your ASP.net MVC project. By convention, MvcCss will render CSS <link> tags for:

  • If it exists, the main site-wide CSS file
  • If it exists, a CSS file that is specific to the current controller (/About* => About.css)
  • If it exists, a CSS file that is specific to the current action (/About/Contact => About-Contact.css)

Why?

CSS gets messy as a site gets big. By sticking to convention, it becomes much more manageable and understood across the team.

My personal best-practice is to have one site-wide CSS, and then an individual CSS file per controller, when called for. (See “CSS optimization” below.)

So?

Really, we are trying to manage scope. With most languages (C#, Javascript), the rules are clear and enforced.

CSS, however, really doesn’t guarantee much in this regard. We’ve all had the experience of making a small change in a massive CSS file, only to discover we broke something else far away. Having controller-scoped CSS files, as above, helps to keep local changes local.

Go on.

OK! How about:

<body class="about" id="about-contact">

Thus allowing us to scope our selectors in CSS, like:

.about div.promo { background-color: yellow; }
#about-contact img.person { float: right; }

MvcCss will take care of this. Simply replace the opening and closing <body> tags in your master page with

<% using (Html.BeginBody()) { %>
<-- The existing ContentPlaceHolders and such -->
<% } %>

…and two attributes will be added to the body tag: a class which represents controller scope, and an id which represents the action scope.

Side note: this dovetails very well with the nesting approaches of LESS and SASS.

What else?

Well, versioning static files is tedious.  Add a boolean like so: <%= Html.MvcCss(true) %> to get a timestamp on the URL, based on the file’s last write date:

<link href="/Content/About.css?a65f44" rel="stylesheet" type="text/css" />

You might like to minimize your css files, too, yes? MvcCss will automatically look for files ending in .min.css, and use those.

<link href="/Content/About.min.css?a65f44" rel="stylesheet" type="text/css" />

Implementation and conventions

You’ve seen most of it above. Since this is new I haven’t packaged official releases yet, please just download the source code and try it in your project.

You’ll need to import the namespace in your master file: <%@ Import Namespace="MvcCss" %>

MvcCss looks for Site.css as the site-wide CSS file. You can override it easily: <%= Html.MvcCss("all.css") %>

As with MVC views, you can skip the file extension and just go with <%= Html.MvcCss("all") %>

MvcCss is hard-coded to look in the following folders in the following order: ~/Content, ~/Content/Css. (Not case-sensitive.)

To make development easier, MvcCss will only choose the .min.css file if debugging is not enabled. And, for safety, if both the regular and minimized css files exist, MvcCss will confirm that the .min.css file is newer before using it (falling back to the regular version if not).

A word about CSS optimization

People have different ideas about the optimal way to organize CSS.

cssauditThere is a conventional wisdom that all of your CSS should be concatenated in one big file, to minimize HTTP requests. What often goes unmentioned is the cost: unused CSS selectors all over your site.

...which means that the browser is querying the DOM a lot more than it needs to.

As a practical matter, I have settled on one specific CSS file per controller, when called for, in addition to the site-wide CSS. An About.css file, for everything under /About, would fit this bill.

This, to me, balances the optimization equation: HTTP requests vs. unused selectors vs. manageability.

Finally

This is all early, pre-1.0 work. Let me know if I am on the right track, matt+dontspam@clipperhouse.com.

Last edited Jan 27, 2011 at 2:40 PM by mwsherman, version 49