Add variables to standard CSS stylesheets in ASP.NET

Oct 9, 2006

After reading a post on The Cafes about the lack of dynamics in the CSS language, I found myself inspired to do something about it. It became clear that something had to be done and that we couldn’t wait for the W3C to come up with a new standard and browser vendors to incorporate them.

There were two things I wanted to enrich the CSS language with:

  • Variables
  • Scripting

To make it work, I created an HttpHandler for ASP.NET that is able to parse these two new features and apply them to the stylesheet. It does not interfere with any CSS standards because everything is handled by the server.

Here is an example of a .css file that uses these new capabilities.

define BACKCOLOR = #00FFFF;

define FORECOLOR = pink;

define TOTALWIDTH = 550;

define RIGHTWIDTH = browser == "IE" & version >= 6 ? "150px" : "170px";

define LEFTWIDTH = TOTALWIDTH / 2 + "px";

 

body{

  background-color: BACKCOLOR;

  color: FORECOLOR;

  width: TOTALWIDTHpx;

}

 

#RightColumn{

  width: RIGHTWIDTH;

}

 

#LeftColumn{

  width: LEFTWIDTH;
}

This will then be outputted as this standard compliant stylesheet:

body{

  background-color: #00FFFF;

  color: pink;

  width: 550;

}

 

#RightColumn{

  width: 150px;

}

 

#LeftColumn{

  width: 275px;

}

All the lines beginning with "define" will be treated as a variable declaration, parsed and then deleted from the parsed stylesheet. Notice the two variable names "browser" and "version". Those two variables are built in properties that return the browser name and major version number respectively.

The scripting syntax is 100% C# because it is compiled by the CodeDom into C# code in-memory. No new syntax of any kind is invented. The reason to use C# and not VB.NET is that developers or designers writing stylesheets probably also know JavaScript which also uses the C style syntax like C#. So, if you are comfortable writing JavaScript, it should be no problem to add scripting to the new CSS variables.

Remember, like JavaScript, these features are case-sensitive. However, that is not why the variable names are upper cased, I just thought it makes sense to let them stick out from the rest of the styles.

Implementation

There are two versions of the HttpHandler – one to use if you can control the IIS and one if you can’t.

If you are able to let the IIS send .css files to the ASP.NET engine you should download the CssHandler.cs file below and add it to the App_Code folder and then add the following to the web.config:

<httpHandlers>

  <add type="CssHandler" verb="*" path="*.css" />
</httpHandlers>

If you can’t access the IIS, you need to download the CssHandler.ashx below and then call that file while passing the .css file as a query string in the URL like this:

<link rel="stylesheet" type="text/css" href="csshandler.ashx?path=style.css" />

Total feature list

This is the total feature list of the handler:

  • Add variables to CSS (feature)
  • Use scripting capabilities (feature)
  • Server and client side caching (performance)
  • Cache dependant of the source .css file (performance)
  • Whitespace and comment removal (performance)
  • Works only on .css files (security)
  • Blocks the System namespace (security)

Word of caution

Because the scripting language is pure C#, it also means that you have to take injection attacks into account. I’ve already made sure that any scripting using anything in the System namespace will be rejected. That means that you can’t use any System.File.IO commands or anything else that has the word “System.” in it. That eliminate probably 99% of the obvious attack surface, but it is not totally secure.

Download

CssHandler (.cs - for IIS access).zip (1,96 KB)
CssHandler (.ashx - for non IIS access).zip (1,91 KB)

* $4.95/month BlogEngine.net Hosting – Click Here!

Comments (19) -

NinjaCross
NinjaCross
10/9/2006 12:31:31 PM #

The whole idea is great and well implemented.
I tested it only on my local server and it's working nicely, and at this moment I can't say nothing concrete about a release running environment, but 2 questions comes in my mind:
1- The CSharpCodeProvider requires a FullTrust executing environment (ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.NETDEVFX.v20.en/CPref0/html/T_Microsoft_CSharp_CSharpCodeProvider.htm) so this wouldn't run on hoster web servers (like Aruba)
2- Isn't it a little too time-consuming to execute the whole css-parsing + CodeDom allocating/executing at every request of the same css ? Maybe some caching strategies at Application level would be suitable and recommendable
Btw, nice work as everytime Smile

Mads Kristensen
Mads Kristensen
10/9/2006 12:36:02 PM #

NinjaCross. I have to look at the FullTrust and find out if it is an issue or not. In regards to the caching issue I can tell you that the whole thing is cached. It only parses the document 1 time and keeps it in memory until the source .css file is updated. Because it also removes whitespace and comments it actually has better performance than regular .css files.

NinjaCross
NinjaCross
10/9/2006 3:07:35 PM #

When you talk about caching are you referring to the code contained in the method "SetHeadersAndCache" ?
All about that method is so good, but you created a correct way to improve performances only in the case of a css that stays the same in every request of the same session (or cookie).
I was talking about a more "dynamic / context-dependent" situation, in which the values of the variables inside the CSS can change between the calls of the same session (say i.e. Request/Page-dependent variables values)
In that case you will re-parse the whole css file every time.
Obviously, you reasonably created a code for your specific needs and not for every possible use-case, and maybe I wouldn't have though too about this issue if my specific needs wouldn't requests it ;)
Again, accept my best compliments for this code, and if you find more info about the FullTrust issue, plz share them with us Smile

Mads Kristensen
Mads Kristensen
10/9/2006 8:33:16 PM #

NinjaCross, that is a very interesting point  about the session and cookies. I didn't think of that scenario at all - silly me. It would be easy just to cache it based on the session id or some other thread dependant value. The real issue is the on-the-fly compilation that somehow need to be cached without destroying the possibility to react differently to each visitor and the changes to that visitors cookies, session etc. during his/her visit. Good point, I'll have to make a version 1.1 based on that. Thanks. Let me know if you have any ideas about how to go about it.

NinjaCross
NinjaCross
10/9/2006 9:13:08 PM #

Your ashx code is well written, so probably this optimization will not be that difficult to apply.
Maybe this will imply some architecture-level considerations, because caching these kind of sessions-shared resources necessary requests the use of the Application object.
A first implementation attempt could be performed simply creating and initializing a CSharpCodeProvider instance for each css found in the web site, and using an hash of their full file name as the key to store them in the Application collection.
In this way in ProcessRequest(HttpContext context) you only have to retrieve the correct one from Application and use it to process only the variables values.
Obviously you also can progressively cache them the first time they are requested..... the concept is to don't load more than once each file and the related CSharpCodeProvider instance.
Hope this help Smile

(btw, is there something wrong with your catpcha control ? I every time have to try several times to post the same message before that the page accept it :S)

 Boris Yeltsin
Boris Yeltsin
10/10/2006 9:36:10 AM #

Excellent job as usual Mads!

Next challenge: Write a HttpHandler that uses the HtmlTidy code to make the horrid ASP.NET output HTML code nice and neat so it's easy to debug. (no whitespace removal allowed ;)

Jeremy
Jeremy
10/10/2006 11:37:03 AM #

I wanted to do CSS variables one time.  I created a totally blank .aspx page.  Then on the page_load I did a response.write() and wrote out the entire stylesheet syntax.  Stylesheet reference can have any extension, so it worked great.

lb
lb
10/10/2006 11:20:41 PM #

You rock Mads.

this is very cool.

the fact that ninjaCross has quickly found a powerful use for it that's outside your plan shows just how clever the original idea is.

when i read the heading of the article i thought about it for a moment and imagined it would be imlemented it an entirely different way. i thought you'd written a custom build provider that was targeting a different file extension (say .cssx) in your visual studio project, to generate static css files at build time. the solution you've got though is better because it allows for more dynamic situations, like the one described by ninjacross.

thanks for sharing your excellent solution Mads

lb

soccerdad
soccerdad
10/11/2006 2:06:02 AM #

What I'd like to see is support for the virtual path notation (e.g. "~/images/page-bg.jpg") within .css files so that url references, etc., are assured to be right no matter where they're deployed or what level of page folder references the .css file. Just today I was thinking about how that is missing from CSS when I'm using it in asp.net apps.

 saj
saj
10/11/2006 5:15:13 PM #

Nice impl, but I think it would be useful to devise a syntax that's more css-like. Very rough idea/straw man:

@using {
  @import url(sitemacros.csst);
  BACKCOLOR: #00FFFF;
  FORECOLOR: pink;
  @apply-macros {
     body{
       background-color: BACKCOLOR;
       color: FORECOLOR;
    }
}
}
The idea is not just to have templates, but perhaps to influence a templating direction for w3c/css.

 Joshua Flanagan
Joshua Flanagan
10/11/2006 5:34:54 PM #

Rory Blyth implemented something very similar a couple years ago. Might want to check it out and compare:
neopoleon.com/.../8705.aspx

 saj
saj
10/11/2006 5:40:14 PM #

...otoh maybe there's enough tech already:
http://www.w3.org/TR/NOTE-XSL-and-CSS

alibotean
alibotean
10/12/2006 9:42:10 AM #

I've used a similar aproach to generate browser specific css output. One problem that I've ran into: if there's a proxy in front of the webserver that caches the css output (let's say for IE) you'll run into problem when subsequent requests (from Firefox) will use the cached css. Any ideas on this ?

 free
free
1/17/2007 9:13:38 AM #

well done

 R Veerman
R Veerman
3/16/2007 4:55:50 PM #

Funny, i've wished for such a feature aswell at some point, but then realized that i just needed to make my IDs and CLASSNAMEs semantical.. Adds to readability too.

 John Manoogian III
John Manoogian III
3/17/2007 7:10:42 PM #

very cool idea! i've always wanted variables within CSS, but never done it. nice work.

RyanOC
RyanOC United States
7/11/2008 1:19:39 PM #

Very nice! Thanks!

Pauli &#216;ster&#248;
Pauli Østerø Denmark
2/11/2009 7:39:24 PM #

#soccerdad
Thats perhaps the easiest thing to do. Just add this to your default variables list

_Variables.Add("~/", HttpRuntime.AppDomainAppVirtualPath);

Then all occurences of ~/ in your css-file will be replaced with /(virtual_path if any)

Pauli &#216;ster&#248;
Pauli Østerø Denmark
2/11/2009 7:42:18 PM #

about the css-handler (ashx) i prefer this style better

css.axd/style.css

and then retrieving the path via PathInfo on the Request.

Pingbacks and trackbacks (1)+

Comments are closed

About the author

Mads Kristensen

Mads Kristensen
Program Manager at the Microsoft Web Platform team and founder of BlogEngine.NET.

More...

Month List

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer’s view in any way.