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 introduction
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.
There
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.