Click here to Skip to main content
Email Password   helpLost your password?

Introduction

User want flexibility in the websites they visit, they want to define what content they see and how it is shown. Website developers want to give them that ability but need to balance it against maintainability. Using Cascading Style Sheet (CSS) the developer can specify certain base layouts, or themes, and allow the user to change these at runtime. However, creating separate files for all possibilities is just not reasonable. A better way would be to change certain CSS values at runtime based on settings specified by the user, yet CSS does not have variables that can be evaluated at runtime.

Solution

To get around the problem of not having variables in CSS one must read the CSS file and replace the given values at runtime. The good news is that in ASP.NET this is a relatively easy task.

body
{
   background-color:#BG_COLOR#
}

Generic HTTPHandler

Using Visual Studio 2005 you can easily add a Generic HTTPHandler.

Add New Item

This will create an ashx file and add it to you project. This file implements the IHTTPHandler interface, with its one and only method, ProcessRequest and includes the WebHandler page directive.

<%@ WebHandler Language="C#" Class="Handler" %>

The .NET Framework treats these files as HTPPHandlers without the need to register them in the <httpHandlers> section of the web.config file.

<%@ WebHandler Language="C#" Class="CSSHandler" %>
using System;
using System.Web;
using System.Configuration;

public class CSSHandler : IHttpHandler 
{
    public void ProcessRequest (HttpContext context) 
    {
        context.Response.ContentType = "text/css";
        
        // Get the file from the query stirng

        string File = context.Request.QueryString["file"];
        
        // Find the actual path

        string Path = context.Server.MapPath(File);
        
        //Limit to only css files

        if(System.IO.Path.GetExtension(Path) != ".css")
           context.Response.End();

        //Make sure file exists

        if(!System.IO.File.Exists(Path))
           context.Response.End();

        // Open the file, read the contents and replace the variables

        using( System.IO.StreamReader css = new System.IO.StreamReader(Path) )
        {
            string CSS = css.ReadToEnd();
            CSS = CSS.Replace("#BG_COLOR#", ConfigurationManager.AppSettings["BGColor"]);
            context.Response.Write(CSS);
        }
    }
    
    public bool IsReusable 
    {
        get { return false; }
    }
}

As we can see the ProcessRequest method simply opens the file specified on the query string, reads it and using string replace to add the value for the variable that has been specified in the web.config file. Not much to it.

<link rel="Stylesheet" href="CSSHandler.ashx?file=default.css" />
<appSettings>

    <add key="BGColor" value="Red"/>

</appSettings>

Limitations

Using a generic webhandler has a disadvantage in that you must specify the style sheet to parse. This breaks down when using ASP.NET 2.0 Themes however because any style sheet placed in the theme folder will automatically be linked, no need manually add it to your web pages. Although you can manually add each one it isn�t a very maintainable model.

Better solution

A better solution is to create a custom HTTPHandler and add it to the httpHandlers section of the web.config file.

<httpHandlers>
    <add verb="*" path="*.css" type="CustomHandler.CSSHandler, CustomHandler"/>
</httpHandlers>
public class CSSHandler : IHttpHandler 
{
#region IHttpHandler Members

    public bool IsReusable
    {
        get { return false; }
    }

    public void ProcessRequest(System.Web.HttpContext context)
    {
        // Get the physical path of the file being processed

        string File = context.Request.PhysicalPath;

        // Open the file, read the contents and replace the variables

        using(System.IO.StreamReader reader = new System.IO.StreamReader(File))
        {
            string CSS = reader.ReadToEnd();
            CSS = CSS.Replace("#BG_COLOR#", ConfigurationManager.AppSettings["BGColor"]);
            context.Response.Write(CSS);
        }
    }

    #endregion
}

 

The only difference from the previous example is that the CSS file to parse is obtained from the context.Request.PhysicalPath property. Since the handler is registered for css files it will process any stylesheet file regardless of its location in the web project.

Conclusion

This article has hopefully shown a method that can be used to provide dynamic settings to an otherwise static file and give website uses a more positive experience.

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralNot working in Firefox
pavithru
4:14 17 Feb '10  
Hi,

Its a great finding, and useful one, but while trying to implement this its not working in FireFox, can any one suggest about this.

Thanks in adavance
GeneralRe: Not working in Firefox
Mark Nischalke
12:40 17 Feb '10  
All processing is done before the files are served to any browser so it is possible your CSS has some setting that are not compatible with Firefox. What is not working?

I know the language. I've read a book. - _Madmatt

GeneralMy vote of 2
BooGhost
10:13 29 Dec '09  
Doesn't bring anything new to the table.
GeneralRe: My vote of 2
Mark Nischalke
12:37 17 Feb '10  
Please elaborate. Where is a similar implementation?

I know the language. I've read a book. - _Madmatt

Generalperformance
liorgold
23:25 31 Jul '07  
hello .
i love the idea of this article and the idea of using a CSS handler.
but it has a problem of performance which i can't figure.
I've seen Eveyatar's response, talking about cache ,
and the author response that the attached source code it not full solution.

i want to solve the performance problem,
but i don't know how to implement it,
and prevent the browser of downloading the CSS file each request.

can you help please?

thank u very much,
lior.
GeneralRe: performance
Mark Nischalke
2:43 1 Aug '07  
"This was a project to demonstrate the concept, not a complete solution for everyone."

The source is a full solution, just not for everyone.

What performance problems are you encountering? What can't you figure out?


only two letters away from being an asset

GeneralRe: performance
Evyatar Ben-Shitrit
9:24 1 Aug '07  
Hi Lior,

All you need to do is to add the following lines on top of your ProcessRequest() method:

context.Response.Cache.SetCacheability(HttpCacheability.Public);
context.Response.Cache.SetVaryByCustom("file");
context.Response.Cache.SetExpires(DateTime.Now.AddDays(1));



-- modified at 16:06 Wednesday 1st August, 2007

Evyatar Ben-Shitrit

Generalare there other versions??
nilskyone
0:13 19 Jun '07  
just asking if there are versions for this using the .net 2003?

thanks and regards to all...

great code though Smile

just learning.
- niel

GeneralRe: are there other versions??
Mark Nischalke
3:03 19 Jun '07  
Sorry but no, generic http handlers are a feature of 2.0. Though the solution using a custom HTTP Handler should work since nothing has really changed there.


only two letters away from being an asset

GeneralRe: are there other versions??
nilskyone
15:37 9 Jul '07  
Thanks...

What i did is old school. I got a control which prints the style tags...

D'Oh! D'Oh! D'Oh!

just learning.
- niel

GeneralRe: are there other versions??
molebrain
4:52 13 Jul '07  
I think you've done a great approach. For what it worth it is good to print out style tags with a control, if it help.

-----------------------------
COM is not dead...Wrapping it is the Answer
-----------------------------

Questionthis is working for localhost but not for online..
rakesh_csit
19:54 24 May '07  
Hello.
This code is excellent...
but i am trying to apply this online.. this is not working.
my according.. when i use http://asd.cmn/asd.css
in this case httphandler is not working..
can u give me solution for this problem. as soon as possible.

Thanks & Regards
Rakesh Yadav

Rakesh Yadav
AnswerRe: this is working for localhost but not for online..
Mark Nischalke
3:12 25 May '07  
Make sure *.css is mapped for the website in IIS.

Don't ask for something "as soon as possible". This is a volunteer site; those of us who submit articles or answer forums have other things to do besides solve your problems. I make every effort to respond on a timely basis, but that is dependent upon my available time.


only two letters away from being an asset

QuestionNot working for me
Saumin
13:05 24 May '07  
hi,
i downloaded your example, and it works on my machine, but when i implement exactly the same code in my web app, the httphandler code doesnt get hit. I have no idea where i am going wrong. Any pointers?

Thanks!
saumin
AnswerRe: Not working for me
Mark Nischalke
16:46 24 May '07  
If you are using the first method you must remember to update the setting for the website to allow *.css files to be processed by the ASP.NET engine.


only two letters away from being an asset

GeneralRe: Not working for me
Saumin
5:52 25 May '07  
I am using your method, where you have the handler code in another Csharp project. Do i have to update the setting for the website to allow *.css files to be processed by the asp.net engine? Please let me know.

Thanks!
Saumin
GeneralRe: Not working for me
Mark Nischalke
6:03 25 May '07  
YES. Was this not clear in my first response?


only two letters away from being an asset

GeneralRe: Not working for me
Saumin
6:23 29 May '07  
Hi Mark,
I have one more problem. I have implemented this in my web application. The web app has a login page which also uses the css. After i registered css in asp.net engine, the login page lost all its styling. In the debug mode, i am seeing CSSHandler code is not being hit. Only when i enter username/password and click on submit button, the handler code is being hit. Do you have any suggestions on how i can fix this?

Thanks!
Saumin
GeneralGreat Work
dbeard
9:10 10 May '07  
This works well for me.

I recently upgraded a web project from 1.1 to 2.0 and I was using a http handler to dynamically replace some values in a css document. The handler still worked in 2.0, but the new xhtml doctype that VS 2005 uses was really messing up my styles in mozilla.

If you use a separate handler and register it in your web.config, don't you have to register the .css application extension in IIS? This was the real problem because the css was getting served as text/html instead of text/css, causing FireFox to render the page in Quirks mode.

Using the Generic Handler you provided here works like a charm. Thanks!
GeneralVariable Source
JoeReynolds
7:56 1 Apr '07  
The "better" method of handling the css files raises a question for me. Maybe the answer is simple but I don't see it.

Anway, the sample shows variables stored in web.config. I have a master page that inherits a basepage where numerous variable values are loaded from database. For example, background color of a page.

With the query string approach I can add those variables to the query string and handle them in the ashx file. However .... how can I send the value of background color and other variables from my basepage on to the handler with the "better" approach.
GeneralRe: Variable Source
Mark Nischalke
14:53 1 Apr '07  
By accessing the database in the handler and substituting the appropriate values.


only two letters away from being an asset

GeneralLots of praise [modified]
jokva
7:58 14 Feb '07  
Thank you so much for this article, and for taking time to share it!

The reason I would like to underline the above is that the comments you have received are unappreciative, unimaginative, and frankly rude.

When someone sits down to give to the community, it is pretty small of you to try to show off through your comments. If you are so brilliant, I suggest you try writing your own articles. What is wrong with you people?!

I know this is from a while back, but I just came across this today. So, sorry for my late remarks.

- But this is good stuff! It is as simple as it is brilliant, and to me it seems to have a lot of positive implications.

I've been playing around with this for a few hours, and in my opinion you have   opened a door to a much better way of managing Themes. It appears this would allow me to dedicate whole sections of my CSS files to specific browser/browser versions (see below).

What I have done so far is to play around with simple whitespace removal. Themes and css friendly controls create a big pile of css files. Even if the size increase from whitespace is marginal, its worth doing when it can be done cheaply. And if I can use a single set of css files on both the development and the production server, I am certainly better off.

A couple of discoveries I would like to share: browsers seem to accept and handle headers on text/css files as well. Hence, you could benefit from adding cache headers. I tried the following headers, and they seem to create the effect I wanted, at least in IE7, FF, and Opera (well, opera goes without saying, as it seems to be caching everything, but..):

Dim reader As System.IO.StreamReader = New System.IO.StreamReader(File)
            Dim CSS As String = reader.ReadToEnd()
            Using (reader)
               'this is not important. this should be regex based
                  CSS = CSS.Replace(Environment.NewLine, " ")
                  CSS = CSS.Replace("   ", " ")
                 
            End Using
            context.Response.Cache.SetExpires(Now.AddDays(10))
            context.Response.Cache.SetValidUntilExpires(True)
            context.Response.Cache.SetLastModified(Now.ToUniversalTime)
            context.Response.Cache.SetMaxAge(New TimeSpan(10, 0, 0, 0))
            context.Response.Cache.SetRevalidation(HttpCacheRevalidation.None)
            context.Response.Cache.SetCacheability(HttpCacheability.Public) //could be serverandprivate, too.
            context.Response.Cache.SetSlidingExpiration(True)
            context.Response.BufferOutput = True
            context.Response.ContentType = "Text/Css"
            context.Response.Write(CSS)

(pardon my vb)

Not sure what would be an ideal configuration of the cache, but I have noted that Firefox seems to go nuts if the ContentType = "Text/Css" is not present.

Looking at the File objects in IE, these headers are added, and the browsers all seem to reuse the cached objects.

Hence, if one can easily add to this varyby browser/version, I suppose this could be a real step towards overcoming all css hacks with asp.net, too?

I imagine one can easily employ regex to handle a css file that uses special comments markup to target specific browsers, e.g.:

/*<Generic>*/
css for all browsers
/*</Generic>*/

/*<ContemporaryBrowsers>*/
css for new browsers
/*</ContemporaryBrowsers>*/

/*<IE6>*/
only for IE6 here.
/*</IE6>*/

etc.

Hope that was constructive. And thank you again!


-- modified at 7:19 Thursday 15th February, 2007
(had to clean it up a little Smile )

Jo



GeneralRe: Lots of praise
Mark Nischalke
9:03 14 Feb '07  
Thanks for the comments. I'm glad you found the article useful, and above all, food for thought. Most here don't want to think, just have it handed to them.:(


only two letters away from being an asset

GeneralSecurity & Performance
Evyatar Ben-Shitrit
20:54 18 Dec '06  
I agree with the previous comment about security issue.
You must validate which content you are going to sent to client!

Two performance improvements here:
1. I am recommending to use regular expressions instate of String.Replace() it is much faster.
2. CSS content is somthing that not chage frequently, so cache here will increase the server performance and the network utilization. I am recommending to cache the output in the server side (vary by param file) inorder to avoid redundant calculations. In addtion I am recommending to define client side caching (for reasonable yet configurable time frame) inorder to avoid redundant HTTP requests.


Evyatar Ben-Shitrit

GeneralRe: Security & Performance
Mark Nischalke
3:10 19 Dec '06  
Agreed. This was a project to demonstrate the concept, not a complete solution for everyone.

Yes, not checking the file extention on the first example was my laziness (and was corrected). As I have pointed out below, however, using the second method, which I point out is a better solution, this is not an issue.


only two letters away from being an asset


Last Updated 17 Dec 2006 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010