Amit's Blog

Sharing Thoughts and Learning

  Home  |   Contact  |   Syndication    |   Login
  43 Posts | 1 Stories | 234 Comments | 14 Trackbacks

News

About Me?
Read it in
Blog Statistics
Proud Member of

Archives

Post Categories

Articles

Book Review

I Visit.

OpenSource Project(s)

When developing a large web application especially if it is Ajax enabled, we often ended up with a larger number of JavaScript and Cascading Stylesheet files and it is quite common that more than one JavaScript file is involved for a single functionality. For example, if you are using DataTable widget of Yahoo User Interface you have to add yahoo-dom-event.js, connection-min.js, dragdrop-min.js, json.js, datasource-beta-min.js, datatable-beta-min.js (as per the example). When a browser encounters an external resource file such as Image, CSS, JS it opens a separate connection to download the file which actually add extra overhead both to server and the browser. To minimize it we can manually combine multiple JS and CSS file into a single large file, but this makes development life harder. Instead we can programmatically combine these files which we will see in the next section.

To combine these resource file we can utilize the Generic Handler (.ashx)  file which comes with the Asp.net, we can also put some directive in the web.config file to conditionally combines the resource files depending upon the settings. First, lets see the web.config file.

<appSettings>
  <add key="UseFileSet" value="true"/>
  <add key="VersionNo" value="1.0"/>
  <add key="FileSet_CSS_Style1" value="Css/StyleSheet1.css,Css/StyleSheet2.css,Css/StyleSheet3.css,Css/StyleSheet4.css" />
  <add key="FileSet_JS_Functionality1" value="Scripts/JScript1.js,Scripts/JScript2.js,Scripts/JScript3.js,Scripts/JScript4.js" />
</appSettings>

As you can see there three kind of settings, first whether we will use the file set which is specified in the consequent settings, second the version number which we will discuss later and third different file sets. Each fileset contains the fileset name as the key and files location in comma separated value. Now lets see how do we embed this resource files in an aspx page:

<% if (ResourceHandler.UseFileSet)
   {
%>
    <link type="text/css" href="ResourceHandler.ashx?v=<%= ResourceHandler.VersionNo %>&fileSet=FileSet_CSS_Style1&type=text/css" rel="Stylesheet"/>
    <script type="text/javascript" src="ResourceHandler.ashx?v=<%= ResourceHandler.VersionNo %>&fileSet=JS_Functionality1&type=application/x-javascript"></script>
<%
   }
   else
   {
%>
    <link href="Css/StyleSheet1.css" rel="stylesheet" type="text/css" />
    <link href="Css/StyleSheet2.css" rel="stylesheet" type="text/css" />
    <link href="Css/StyleSheet3.css" rel="stylesheet" type="text/css" />
    <link href="Css/StyleSheet4.css" rel="stylesheet" type="text/css" />
    <script type="text/javascript" src="Scripts/JScript1.js"></script>
    <script type="text/javascript" src="Scripts/JScript2.js"></script>
    <script type="text/javascript" src="Scripts/JScript3.js"></script>
    <script type="text/javascript" src="Scripts/JScript4.js"></script>
<%
   }
%>

As you can see we are using Asp.net syntax to conditionally add these resource file depending upon the settings. When we are in development mode we can turn it off  from the web.config file. But the problem with using generic resource handler is the files never gets cached in the browser. So our generic handler must also inject proper caching headers along with combining files. Now lets see how the generic handler is implemented:

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

public class ResourceHandler : IHttpHandler
{
    public static bool UseFileSet
    {
        get
        {
            return Convert.ToBoolean(ConfigurationManager.AppSettings["UseFileSet"]);
        }
    }

    public static string VersionNo
    {
        get
        {
            return ConfigurationManager.AppSettings["VersionNo"];
        }
    }

    public bool IsReusable
    {
        get
        {
            return true;
        }
    }

    public void ProcessRequest (HttpContext context)
    {
        string fileSet = context.Request.QueryString["fileSet"];

        //Basic Validation
        if (string.IsNullOrEmpty(fileSet))
        {
            return;
        }

        string contetType = context.Request.QueryString["type"];

        if (string.IsNullOrEmpty(contetType))
        {
            return;
        }

        string files = ConfigurationManager.AppSettings["FileSet_" + fileSet];

        if (string.IsNullOrEmpty(files))
        {
            return;
        }

        //Get the list of files specified in the FileSet
        string[] fileNames = files.Split(',');

        if ((fileNames != null) && (fileNames.Length > 0))
        {
            //Write each files in the response
            for (int i = 0; i < fileNames.Length; i++)
            {
                context.Response.Write(File.ReadAllText(context.Server.MapPath(fileNames[i])));
            }

            //Set the content type
            context.Response.ContentType = contetType;

            // Cache the resource for 7 Days
            CacheUtility.Cache(TimeSpan.FromDays(7));
        }
    }
}

As we can see the Handler contains some static property which we used when embedding in the aspx files.  The file merging is done in the ProcessRequest method. In this method we are reading the physical files which is specified in the web.config fileset and writing it in the Asp.net Response object and at the end we are using a helper class CacheUtility to cache the response. Now let see how the this class is implemented:

using System;
using System.Web;
using System.Reflection;

public static class CacheUtility
{
    public static void Cache(TimeSpan duration)
    {
        HttpCachePolicy cache = HttpContext.Current.Response.Cache;

        FieldInfo maxAgeField = cache.GetType().GetField("_maxAge", BindingFlags.Instance | BindingFlags.NonPublic);
        maxAgeField.SetValue(cache, duration);

        cache.SetCacheability(HttpCacheability.Public);
        cache.SetExpires(DateTime.Now.Add(duration));
        cache.SetMaxAge(duration);
        cache.AppendCacheExtension("must-revalidate, proxy-revalidate");
    }
}

As you can see we are not only using the regular caching methods of Asp.net, we are also using some reflection code to set the proper cache-control header. You can find the reason why the reflection is used from these two links Client-side caching for script methods access in ASP.NET AJAX and ASP.NET AJAX under the hood secrets. If you want to know more about the cache-control header then read Caching Details. If any of the files get modified then update the version number in web.config, it will ensure that the latest files get downloaded due to the query string.

Download: Complete Solution

kick it on DotNetKicks.com

posted on Wednesday, July 25, 2007 3:57 PM

Feedback

# re: Combine Multiple JavaScript and CSS Files and Remove Overheads 7/29/2007 3:22 AM Anna Bay
Wow...I must say that it was still difficult for me because of certain reasons, but after reading your post I got better with this sort of stuff!!!Thanks


# re: Combine Multiple JavaScript and CSS Files and Remove Overheads 7/29/2007 4:03 AM David
Great, but how about implementing Compression?

# re: Combine Multiple JavaScript and CSS Files and Remove Overheads 7/31/2007 12:46 AM ReTox
I would add just one more thing:

instead of

Css/StyleSheet1.css

use

~/Css/stylesheet1.css

and resolve paths with VirtualPathUtility class (framework 2.0). This way linked files will have proper paths in all subfolders of web application.

# re: Combine Multiple JavaScript and CSS Files and Remove Overheads 12/27/2007 6:47 PM Angel Eyes
This is cool, and I also second the motion to have optional compression with this.

# re: Combine Multiple JavaScript and CSS Files and Remove Overheads 3/25/2008 6:56 PM Arpit
Can you suggest me, how i can apply same thing in jsp files

# re: Combine Multiple JavaScript and CSS Files and Remove Overheads 4/7/2008 5:35 PM Anil
Hi,

I tried to use your solution but the asp tags are not getting replaced when the link tag is inside the head.

Is this s known problem?


Thanks,
Anil



# re: Combine Multiple JavaScript and CSS Files and Remove Overheads 7/20/2008 1:41 PM Acaz
this line:
<link type="text/css" href="ResourceHandler.ashx?v=<%= ResourceHandler.VersionNo %>&fileSet=FileSet_CSS_Style1&type=text/css" rel="Stylesheet"/>

is

<link type="text/css" href="ResourceHandler.ashx?v=<%= ResourceHandler.VersionNo %>&fileSet=CSS_Style1&type=text/css" rel="Stylesheet"/>


# re: Combine Multiple JavaScript and CSS Files and Remove Overheads 8/2/2008 11:40 PM ilias hossain
Great. However what do you think about the asp.net theme.


# re: Combine Multiple JavaScript and CSS Files and Remove Overheads 10/21/2008 8:50 PM Moiz Dhanji
Good Approach! but try following:

http://www.codeproject.com/KB/aspnet/AspNetOptimizer.aspx


# re: Combine Multiple JavaScript and CSS Files and Remove Overheads 12/4/2008 5:15 PM Babu Kumarasamy
Hi,

I want some of .js, .css files to be loaded before HTML contents. How can I able to do with your code.

# re: Combine Multiple JavaScript and CSS Files and Remove Overheads 1/16/2009 2:46 AM mark
I've tried that, with slight modification for my own css and js files, but at render i get an error:

CS0103: The name 'ResourceHandler' does not exist in the current context

Not sure how to solve this. Any thoughts?

# re: Combine Multiple JavaScript and CSS Files and Remove Overheads 1/16/2009 4:01 AM mark
What are the steps to impliment this with 3.5 rather than 2.0?

Post A Comment
Title:
Name:
Email:
Website:
Comment:
Verification: