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:
- Within the
configSections
element, add:
<section name="optimizerSection"
type="CnCssJscriptOptimizer.ConfigurationSections.OptimizerSection,
CnCssJscriptOptimizer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
- Within the
configuration
element, add the optimizerSection
. For example:
<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.
- 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
.
- Register the controls in your *.aspx, *.ascx, and/or *.master files via:
<%@ Register Assembly="CnCssJscriptOptimizer"
Namespace="CnCssJscriptOptimizer.Controls" TagPrefix="cc1" %>
- In your *.aspx file, place the
StyleSheetCombiner
and ScriptCombiner
control tags around your CSS and script declarations. For example:
<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:
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:
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