Archive

Posts Tagged ‘optimization’

Compacting CSS on-the-fly

July 2nd, 2009 No comments

I’m currently doing quite a lot of work on my ny MVC-version of Imager Gallery. I still have a lot to do before I have anything to release (or even use myself) though but I thought I could release a few snippets that I’ve found useful.

First out is a HttpHandler that will process .css-files and remove any unused whitespace from the file before sending it to the client. I’ve been meaning to write this for a long time but never really had the time until someone posted a link about it over at ASPSidan.se showing their own implementation. Reading up on his post about it I found that it was based on a solution by Sam Collett. Both implementations lacked a few things that I wanted to have so I sat down with Sams code and started improving it, mainly making it a work as a handler for .css-files instead of a Generic Handler wanting a querystring but also adding caching and compiling the regular expression as a static member.

To use this, just pop it in a .cs-file in your your App_Code folder and add it to your web.config (as described in the comments for the class). You also need to configure your IIS to route .css-files through the asp.net process to make it run (not needed under IIS7 or IIS6 configured for MVC). Hope you find it useful!

using System;
using System.IO;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Caching;

namespace HttpHandlers
{
    /// <summary>
    /// Handler for compacting css-files on the fly
    /// Based on solution by Sam Collett found at
    /// http://webdevel.blogspot.com/2007/09/csscompact-webhandler-for-shrinking-css.html
    ///
    /// Needs to be added to web.config under System.Web/httpHandlers like this
    /// <add verb="GET,HEAD" path="*.css" type="HttpHandlers.CssCompactHandler"/>
    ///
    /// For debuing purposes you can append a querystring whenbrowsing to your .css-file
    /// to skip the compacting. It would look like this:
    /// http://mySite/Content/Site.css?noCompact=1
    /// </summary>
    public class CssCompactHandler : IHttpHandler
    {
        // Staticly compiled regex to avoid compiling it each request
        private static Regex RegexRemoval = new Regex(@"^\s+|/\*([^*\\\\]|\*(?!/))+\*/|\r|\n|\t", RegexOptions.Multiline | RegexOptions.Compiled);

        public void ProcessRequest(HttpContext context)
        {
            HttpResponse Response = context.Response;
            HttpRequest Request = context.Request;
            Cache Cache = context.Cache;

            FileInfo fileInfo = new FileInfo(Request.PhysicalPath);
            if (!fileInfo.Exists)
            {
                Response.StatusDescription = "The server has not found anything that matches the requested URI.";
                Response.StatusCode = 404;
                Response.End();
            }

            Response.ContentType = "text/css";
            Response.Cache.SetLastModified(fileInfo.LastWriteTime);

            // If we are in debugmode (set in web.config) then
            // we dont want to cache the css since we are probably
            // still in development
            if (context.IsDebuggingEnabled)
            {
                Response.Cache.SetCacheability(HttpCacheability.NoCache);
            }
            else
            {
                Response.Cache.SetCacheability(HttpCacheability.Public);
                Response.Cache.SetExpires(DateTime.Now.AddDays(1));
            }

            // HEAD requests only need the headers so we skip the
            // content here
            if (Request.HttpMethod != "HEAD")
            {
                string cacheName = string.Format("CssCompactHandlerCache_{0}", fileInfo.FullName);
                string cacheValue = Cache[cacheName] as string;
                if (cacheValue != null && Request.QueryString["noCompact"] == null)
                {
                    Response.Write(cacheValue);
                    Response.End();
                }

                using (StreamReader cssStream = fileInfo.OpenText())
                {
                    string cssContent = cssStream.ReadToEnd();
                    if (Request.QueryString["noCompact"] == null)
                    {
                        cssContent = RegexRemoval.Replace(cssContent, "");

                        // Set up a CacheDependency on the css-file,
                        // this way the cache will be invalidated if
                        // we change the file
                        CacheDependency dependency = new CacheDependency(fileInfo.FullName);
                        Cache.Add(string.Format("CssCompactHandlerCache_{0}", fileInfo.FullName), cssContent, dependency, DateTime.Now.AddYears(1), Cache.NoSlidingExpiration, CacheItemPriority.AboveNormal, null);
                    }
                    Response.Write(cssContent);
                }
            }
        }

        public bool IsReusable
        {
            get
            {
                return true;
            }
        }
    }
}