Geeks With Blogs
Amit's Blog Sharing Thoughts and Learning

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 Asp.net , Tips/Tricks | Back to top


Comments on this post: Combine Multiple JavaScript and CSS Files and Remove Overheads

# re: Combine Multiple JavaScript and CSS Files and Remove Overheads
Requesting Gravatar...
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
Left by Anna Bay on Jul 29, 2007 3:22 AM

# re: Combine Multiple JavaScript and CSS Files and Remove Overheads
Requesting Gravatar...
Great, but how about implementing Compression?
Left by David on Jul 29, 2007 4:03 AM

# re: Combine Multiple JavaScript and CSS Files and Remove Overheads
Requesting Gravatar...
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.
Left by ReTox on Jul 31, 2007 12:46 AM

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

# re: Combine Multiple JavaScript and CSS Files and Remove Overheads
Requesting Gravatar...
Can you suggest me, how i can apply same thing in jsp files
Left by Arpit on Mar 25, 2008 6:56 PM

# re: Combine Multiple JavaScript and CSS Files and Remove Overheads
Requesting Gravatar...
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

Left by Anil on Apr 07, 2008 5:35 PM

# re: Combine Multiple JavaScript and CSS Files and Remove Overheads
Requesting Gravatar...
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"/>
Left by Acaz on Jul 20, 2008 1:41 PM

# re: Combine Multiple JavaScript and CSS Files and Remove Overheads
Requesting Gravatar...
Great. However what do you think about the asp.net theme.
Left by ilias hossain on Aug 02, 2008 11:40 PM

# re: Combine Multiple JavaScript and CSS Files and Remove Overheads
Requesting Gravatar...
Good Approach! but try following:

http://www.codeproject.com/KB/aspnet/AspNetOptimizer.aspx
Left by Moiz Dhanji on Oct 21, 2008 8:50 PM

# re: Combine Multiple JavaScript and CSS Files and Remove Overheads
Requesting Gravatar...
Hi,

I want some of .js, .css files to be loaded before HTML contents. How can I able to do with your code.
Left by Babu Kumarasamy on Dec 04, 2008 5:15 PM

# re: Combine Multiple JavaScript and CSS Files and Remove Overheads
Requesting Gravatar...
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?
Left by mark on Jan 16, 2009 2:46 AM

# re: Combine Multiple JavaScript and CSS Files and Remove Overheads
Requesting Gravatar...
What are the steps to impliment this with 3.5 rather than 2.0?
Left by mark on Jan 16, 2009 4:01 AM

Your comment:
 (will show your gravatar)


Copyright © Kazi Manzur Rashid | Powered by: GeeksWithBlogs.net | Join free