Click here to Skip to main content
15,861,172 members
Articles / Web Development / CSS

Automatically Minify, Combine, Compress, and Cache *.js and *.css Files in your ASP.NET Project

Rate me:
Please Sign up or sign in to vote.
4.74/5 (17 votes)
24 Mar 2010CPOL5 min read 152.7K   3.1K   106   34
Automatically minify, combine, compress, and cache .js and .css files in your ASP.NET project

Introduction

This article shows you how to optimize your style sheet and JavaScript files in your ASP.NET web application. More specifically, you will be shown that all of your style sheet and JavaScript files in your ASP.NET web application can be automatically minified, compressed, combined, and cached.  

Background 

Each time you have script or link tag in a web page to reference a JavaScript or style sheet file, a separate request is made by the browser to get that file. This results in much network latency. A more efficient approach would be to have just one request to get all of the JavaScript files, and another request to get all of the script files for the page. I have accomplished this by using an HTTP handler that will return the required content of each set of script or style sheet files. This content will also be minified, compressed, and cached on both the browser and the server, along with the correct file dependencies so that any changes to the files will invalidate both the client and server cache.  

I'd like to thank Moiz Dhanji for his article.

I used much of his code to develop this project. The main additions I made were the ability to handle CSS files, file dependency caching, and custom controls for easy portability to other projects. The main subtractions were the profiler feature, and the ability to handle embedded resource files. 

I am aware that the Ajax toolkit's ScriptManager control can combine script files and it may be suitable for your needs, BUT all it does is combine them. My project can do this, plus minify, compress, and cache them too. My project can also handle CSS files. Furthermore, the ScriptManager forces you to download all of the ASP.NET Ajax script files, which you may not need.  

Implementation 

Created Section for Web.config

The ScriptCombinerSection class does this.  It is basically the same code as Moiz Dhanji's, except with some unnecessary attributes removed. 

Created Custom Handler

The CssJscriptOptimizerHandler class performs the main functionality of the project.  It does the minifiying, compressing, combining, and caching of the files.

Created Custom Controls

The ScriptCombiner and StyleSheetCombiner controls are responsible for inspecting their enclosed script and link tags, respectively, and then rendering the appropriate single request URL that will point to the custom handler for processing. 

Paths, Paths, Paths

Another goal that I had for this project was to make it transparent to the script and style sheet developers. I didn't want them to have to modify the way they work to accompany me in any way. One of the things about CSS is that there is the URL attribute. This attribute has no concept of the application root (~) that ASP.NET does. So a lot of times, there are relative paths like "../Images/image1.jpg" in the files.  This won't work if the handler is in a different directory than the CSS file.  To address this issue, I implemented a FixUrlPaths method my custom handler to calculate the correct path when it sees the URL attribute.

How To Use

In the web.config file:

  1. Within the configSections element, add:
    XML
     <section name="optimizerSection" 
              type="CnCssJscriptOptimizer.ConfigurationSections.OptimizerSection,   
     CnCssJscriptOptimizer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
  2. Within the configuration element, add the optimizerSection. For example:
    XML
            <optimizerSection
              enable="true"
              enableScriptCompression="true"
              enableSheetCompression="true"
              enableScriptMinification="true"
              enableSheetMinification="true"
              fullHandlerPath="~/CnScriptResource.ashx"
              >
                <add key="1" path="~/Scripts/Script01.js" />
                <add key="2" path="~/Scripts/Script02.js" />
                <add key="3" path="~/Scripts/Script03.js" />
                <add key="4" path="~/Styles/test1.css" />
                <add key="5" path="~/Styles/test2.css" />
                <add key="6" path="~/Styles/test3.css" />
            </optimizerSection>

    where the fullHandlerPath is the path to the name of your httphandler that derives from CssJscriptOptimizerHandler and where the key is any arbitrary and URL-friendly unique key value,  and where the path is the path to the script or CSS file in your project. Note that all your script and CSS files that you desire to "C4" in your project should be listed here. Also note that all paths should start with the "~".
    Note that setting the attribute enable="false" will turn off the optimizer.

  3. Create a new handler file for your web project.  Have it derive from CssJscriptOptimizerHandler. The path to this file should be stated in the fullHandlerPath attribute of the optimizerSection.
  4. Register the controls in your *.aspx, *.ascx, and/or *.master files via:
    XML
       <%@ Register Assembly="CnCssJscriptOptimizer" 
    	Namespace="CnCssJscriptOptimizer.Controls" TagPrefix="cc1" %>
  5. In your *.aspx file, place the StyleSheetCombiner and ScriptCombiner control tags around your CSS and script declarations. For example:
    HTML
    <cc1:StyleSheetCombiner ID="sheetCombiner" runat="server">
    <link rel="stylesheet" href="Styles/test1.css" />
    <link rel="stylesheet" href="Styles/test1.css" />
    <link rel="stylesheet" href="Styles/test1.css" />
    <link rel="stylesheet" href="Styles/test1.css" />
    <link rel="stylesheet" href="Styles/test1.css" />
    <link rel="stylesheet" href="Styles/test1.css" />
    <link rel="stylesheet" href="Styles/test1.css" />
    <link rel="stylesheet" href="Styles/test1.css" />
    <link rel="stylesheet" 
    	href='<%# sheetCombiner.ResolveUrl("~/Styles/test2.css")%>' />
    <link rel="stylesheet" 
    	href='<%# sheetCombiner.ResolveUrl("~/Styles/test3.css")%>' />
    	
    </cc1:StyleSheetCombiner>       
    	
    <cc1:ScriptCombiner ID="scriptCombiner" runat="server">
    
    <script src='<%# scriptCombiner.ResolveUrl("~/Scripts/Script01.js")%>' 
            type="text/javascript"></script>
    <script src='<%# scriptCombiner.ResolveUrl("~/Scripts/Script02.js")%>' 
            type="text/javascript"></script>
    <script src="Scripts/Script03.js" type="text/javascript"></script>
    
    </cc1:ScriptCombiner>

    Note: Do not use <%= controlName.ResolveUrl("~/someUrl")%>, but the data binding version <%# controlName.ResolveUrl("~/someUrl")%> instead.  controlName.DataBind() is called internally by the control.

    If any of your stylesheet links makes use of the media or title attribute, then you may not want to include those within the StyleSheetCombiner control because these attributes will be lost, resulting in improper CSS rendering.

Performance

Here is a screenshot with the optimizer enabled:

OptimizerEnabled.JPG

Notice in the picture above that there are two requests made to ResoureHandler.ashx.  The first one is to get all the style sheets. The second one is to retrieve all the JavaScript files.  Total response time (from my slow dev machine and server) for both requests is 2038 milliseconds.

And here is a screenshot with the optimizer not used:

NoOptimizer.JPG

In the above picture, notice that a separate request is made for each script and style sheet file. Total response time to get all the files is over 6000 milliseconds. Now I know that the files are not zipped in this case (no zip feature on the dev server), and that this total may also be misleading because the web server might be processing these requests on multiple threads, but I believe that most browsers can only make, at most, two simultaneous requests for a given page. Therefore, I believe it's safe to say that the optimizer still offers improved performance in most scenarios. You'll have to try it in your environment and see for yourself. The optimizer definitely will cut down on the number of times a client will have to hit your site to get each page.  

Conclusion

This article shows you how to optimize your JavaScript and CSS files in your ASP.NET application by automatically minifying, combining, compressing, and caching them. The library code along with a sample test web application is provided for you. Happy coding.  

History

  • 23rd February, 2009: Initial post
  • 2nd March, 2009: Updated download file
  • 24th March, 2010: Updated download file

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionSystem.OutOfMemoryException Pin
Mohammad Maleki1-Jul-20 21:45
Mohammad Maleki1-Jul-20 21:45 
QuestionProblema Merge files CSS and JS Pin
René Hernández19-Feb-16 8:06
René Hernández19-Feb-16 8:06 
QuestionCan we cache httphandler's response? Pin
Sangram Nandkhile15-Feb-16 22:10
Sangram Nandkhile15-Feb-16 22:10 
SuggestionMicrosoft has released their Microsoft Ajax Minifier tool http://ajaxmin.codeplex.com Pin
defza29-Nov-11 2:15
defza29-Nov-11 2:15 
QuestionExcellent! Pin
Sunasara Imdadhusen22-Nov-11 21:50
professionalSunasara Imdadhusen22-Nov-11 21:50 
QuestionCssMinifier bug Pin
alexandrem_blah27-Jul-11 4:08
alexandrem_blah27-Jul-11 4:08 
GeneralHelp with "Create a new handler file for your web project. " Pin
Heather Floyd29-Apr-11 11:33
Heather Floyd29-Apr-11 11:33 
GeneralRe: Help with "Create a new handler file for your web project. " Pin
jeff chin xyz1-May-11 17:40
jeff chin xyz1-May-11 17:40 
GeneralRe: Help with "Create a new handler file for your web project. " [modified] Pin
Heather Floyd2-May-11 5:04
Heather Floyd2-May-11 5:04 
GeneralThere's a problem with re-writing paths in css files. [modified] Pin
Mohammad Mahdi Saffari21-Sep-10 7:31
Mohammad Mahdi Saffari21-Sep-10 7:31 
GeneralIntellisense! Pin
Mohammad Mahdi Saffari8-Sep-10 6:38
Mohammad Mahdi Saffari8-Sep-10 6:38 
GeneralRe: Intellisense! Pin
RichardHowells21-Aug-11 2:02
RichardHowells21-Aug-11 2:02 
GeneralRe: Intellisense! Pin
Mohammad Mahdi Saffari1-Jan-12 3:04
Mohammad Mahdi Saffari1-Jan-12 3:04 
GeneralShould this work with someting like Dojo Toolkit dynamic loading of js files? EOM Pin
iceball1224-Mar-10 23:48
iceball1224-Mar-10 23:48 
GeneralFixed 3 bugs in FixUrlPaths function to make it work with jQuery UI 1.8 css Pin
alaa9jo24-Mar-10 5:05
alaa9jo24-Mar-10 5:05 
GeneralRe: Fixed 3 bugs in FixUrlPaths function to make it work with jQuery UI 1.8 css Pin
jeff chin xyz24-Mar-10 10:10
jeff chin xyz24-Mar-10 10:10 
GeneralRe: Fixed 3 bugs in FixUrlPaths function to make it work with jQuery UI 1.8 css Pin
alaa9jo25-Mar-10 1:58
alaa9jo25-Mar-10 1:58 
That update is working fine except that it doesn't work well with this cssclass:

.ui-widget-overlay { background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); }


in the updated code,it will change that class to this:

.ui-widget-overlay { background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png); }

After digging into the issue,I discovered that changing urlRegex to the following will solve the issue:
Regex urlRegex = new Regex(@"url\([^\s;]*"); //matchin any url entry


In the end there are only two types of urls:
1)one that ends with semi-colon directly: url(....);
2)one that followed with a space then either other properties like in ui-widget-overlay or a space like:
url(....) ;
QuestionPerfect,well done BUT Pin
alaa9jo23-Mar-10 4:55
alaa9jo23-Mar-10 4:55 
AnswerRe: Perfect,well done BUT Pin
jeff chin xyz24-Mar-10 7:58
jeff chin xyz24-Mar-10 7:58 
GeneralsheetCombiner Pin
friendsterjoel3-Mar-10 19:16
friendsterjoel3-Mar-10 19:16 
GeneralRe: sheetCombiner Pin
jeff chin xyz8-Mar-10 7:57
jeff chin xyz8-Mar-10 7:57 
GeneralGreat script! Pin
Immobilis29-Nov-09 2:16
Immobilis29-Nov-09 2:16 
QuestionNot able to download Pin
AswinKrishnan20-Jul-09 20:20
AswinKrishnan20-Jul-09 20:20 
AnswerRe: Not able to download Pin
jeff chin xyz20-Jul-09 20:28
jeff chin xyz20-Jul-09 20:28 
QuestionCan we also cache and minify the ajax's javascript? Pin
arbound0821-May-09 0:11
arbound0821-May-09 0:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.