Introduction
This article explains how to improve application performance using HTTP Handlers and shows it with an example.
To reduce the page load time of the web pages in our application, we can compress content, cache them and then load them on demand. Each time a CSS or a script file is loaded, it takes time for the browser to download the content. That slows down the application until the loading of the CSS or scripts are over. We can use a HTTP Handler to combine the script and CSS files, cache them and then load the compressed content for faster page load in our application. The first time the files are requested, they will be loaded into the cache and then served; each subsequent requests for those files would then be served from the cache only. This would reduce the load time and provide much better responsiveness since the application wouldn't have to wait for the files to be loaded from the disk onto memory and then to the browser's context. Fetching content in this way would improve the application's performance to a considerable extent. We can use a HttpHandler that, when injected into the response stream, would compress the content, cache it and then fetch it to the web page.
Let us start using the Handler
Using this handler is also simple. All we need is a few simple steps outlined below.
1. Include the handler in the web project as in the following:
- #region Namespaces
- using System;
- using System.Net;
- using System.IO;
- using System.IO.Compression;
- using System.Text;
- using System.Web;
- #endregion
- namespace PerformanceOptimizationDemo
- {
- /// <summary>
- /// CompressFiles handler for compressing scripts and style sheets and storing them in the cache.
- /// </summary>
- public class CompressFiles : IHttpHandler
- {
- #region Data members
- private const bool COMPRESSCONTENT = true;
- private const string CACHE_KEY_PREFIX = "HttpCompress.";
- private readonly static TimeSpan CACHE_DURATION = TimeSpan.FromHours(10);
- #endregion
- /// <summary>
- ///
- /// </summary>
- /// <param name="context"></param>
- public void ProcessRequest(HttpContext context)
- {
- HttpRequest request = context.Request;
- // Read setName, contentType and version. All are required. They are
- // used as cache key
- string setName = request["s"] ?? string.Empty;
- string contentType = request["t"] ?? string.Empty;
- string version = request["v"] ?? string.Empty;
- // Decide if browser supports compressed response
- bool isCompressed = COMPRESSCONTENT && this.IsGZipEnabled(context.Request);
- // Response is written as UTF8 encoding. If you are using languages like
- // Arabic, you should change this to proper encoding
- UTF8Encoding encoding = new UTF8Encoding(false);
- // If the set has already been cached, write the response directly from
- // cache. Otherwise generate the response and cache it
- if (!this.WriteBytesFromCache(context, setName, version, isCompressed, contentType))
- {
- using (MemoryStream memoryStream = new MemoryStream(5000))
- {
- // Decide regular stream or GZipStream based on whether the response
- // can be cached or not
- using (Stream writer = isCompressed ?
- (Stream)(new GZipStream(memoryStream, CompressionMode.Compress)) :
- memoryStream)
- {
- // Load the files defined in <appSettings> and process each file
- string setDefinition =
- System.Configuration.ConfigurationManager.AppSettings[setName] ?? "";
- string[] fileNames = setDefinition.Split(new char[] { ',' },
- StringSplitOptions.RemoveEmptyEntries);
- foreach (string fileName in fileNames)
- {
- byte[] fileBytes = this.GetFileContent(context, fileName.Trim(), encoding);
- writer.Write(fileBytes, 0, fileBytes.Length);
- }
- writer.Close();
- }
- // Cache the combined response so that it can be directly written
- // in subsequent calls
- byte[] responseBytes = memoryStream.ToArray();
- context.Cache.Insert(GetKeyFromCache(setName, version, isCompressed),
- responseBytes, null, System.Web.Caching.Cache.NoAbsoluteExpiration,
- CACHE_DURATION);
- // Generate the response
- this.WriteBytes(responseBytes, context, isCompressed, contentType);
- }
- }
- }
- #region Private methods
- /// <summary>
- ///
- /// </summary>
- /// <param name="context"></param>
- /// <param name="virtualPath"></param>
- /// <param name="encoding"></param>
- /// <returns></returns>
- private byte[] GetFileContent(HttpContext context, string virtualPath, Encoding encoding)
- {
- if (virtualPath.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase))
- {
- using (WebClient client = new WebClient())
- {
- return client.DownloadData(virtualPath);
- }
- }
- else
- {
- string physicalPath = context.Server.MapPath(virtualPath);
- byte[] bytes = File.ReadAllBytes(physicalPath);
- return bytes;
- }
- }
- /// <summary>
- ///
- /// </summary>
- /// <param name="context"></param>
- /// <param name="setName"></param>
- /// <param name="version"></param>
- /// <param name="isCompressed"></param>
- /// <param name="contentType"></param>
- /// <returns></returns>
- private bool WriteBytesFromCache(HttpContext context, string setName, string version,
- bool isCompressed, string contentType)
- {
- byte[] responseBytes = context.Cache[GetKeyFromCache(setName, version, isCompressed)] as byte[];
- if (null == responseBytes || 0 == responseBytes.Length) return false;
- this.WriteBytes(responseBytes, context, isCompressed, contentType);
- return true;
- }
- /// <summary>
- ///
- /// </summary>
- /// <param name="bytes"></param>
- /// <param name="context"></param>
- /// <param name="isCompressed"></param>
- /// <param name="contentType"></param>
- private void WriteBytes(byte[] bytes, HttpContext context,
- bool isCompressed, string contentType)
- {
- HttpResponse response = context.Response;
- response.AppendHeader("Content-Length", bytes.Length.ToString());
- response.ContentType = contentType;
- if (isCompressed)
- response.AppendHeader("Content-Encoding", "gzip");
- context.Response.Cache.SetCacheability(HttpCacheability.Public);
- context.Response.Cache.SetExpires(DateTime.Now.Add(CACHE_DURATION));
- context.Response.Cache.SetMaxAge(CACHE_DURATION);
- context.Response.Cache.AppendCacheExtension("must-revalidate, proxy-revalidate");
- response.OutputStream.Write(bytes, 0, bytes.Length);
- response.Flush();
- }
- /// <summary>
- ///
- /// </summary>
- /// <param name="request"></param>
- /// <returns></returns>
- private bool IsGZipEnabled(HttpRequest request)
- {
- string acceptEncoding = request.Headers["Accept-Encoding"];
- if (!string.IsNullOrEmpty(acceptEncoding) &&
- (acceptEncoding.Contains("gzip") || acceptEncoding.Contains("deflate")))
- return true;
- return false;
- }
- /// <summary>
- ///
- /// </summary>
- /// <param name="setName"></param>
- /// <param name="version"></param>
- /// <param name="isCompressed"></param>
- /// <returns></returns>
- private string GetKeyFromCache(string setName, string version, bool isCompressed)
- {
- return CACHE_KEY_PREFIX + setName + "." + version + "." + isCompressed;
- }
- #endregion
- /// <summary>
- /// IsReusable property
- /// </summary>
- public bool IsReusable
- {
- get
- {
- return true;
- }
- }
- }
- }
- <?xml version="1.0"?>
- <configuration>
- <appSettings>
- <add key="Css_Files" value="~/Styles/Css1.css,~/Styles/Css2.css,~/Styles/jquery.ribbon.css,~/Styles/liteaccordion.css"/>
- <add key="Script_Files" value="~/Scripts/Js1.js,~/Scripts/Js2.js,~/Scripts/jquery.js"/>
- </appSettings>
- <system.web>
- <compilation debug="true" targetFramework="4.0" />
- </system.web>
- </configuration>
- <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Demo.aspx.cs" Inherits="PerformanceOptimizationDemo.Demo" %>
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
- <html xmlns="http://www.w3.org/1999/xhtml">
- <head runat="server">
- <title>Demo Application</title>
- <link type="text/css"
- rel="Stylesheet"
- href="CompressFiles.ashx?s=Css_Files&t=text/css&v=1" />
- </head>
- <body>
- <script type="text/javascript"
- src="CompressFiles.ashx?s=Script_Files&t=type/javascript&v=1" >
- </script>
- <form id="form1" runat="server">
- <div>
- </div>
- </form>
- </body>
- </html>
Summary
In this article we have learned how to optimize application Performance using the HTTP Handler. Please find the attached source code.

Join the conversation! Your thoughts help the community grow.