I am now at the stage in my project where I need to find ways of fine tune the loading times. Of course I have used tools like Firbug etc to run diagnosis on what is taking a long time to load etc. One thing I did find, was that a lot of the time I was using .ASPX files to load something that really only need a method call, and not all the overhead that comes with an ASPX page. So what else to use, but a custom HTTP Handler!
e instead of just adding a reference within the ASPX page and making .net do all the work. The payoff, was a load time of the CSS that was about half!!!!
Here is how you do it.
Create a Generic Handler file in your Website or Web Application.
By default you only have to implement one method and a property when implementing the IHttpHandler interface
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.IO.Compression;
public class CSSHttpHandler : IHttpHandler {
private const string PrefixAllImagesWith = "~/";
private readonly static TimeSpan KeepInCache = TimeSpan.FromDays(15);
private readonly static Regex UrlCheck = new Regex(@"http(s)?://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?", RegexOptions.Compiled);
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/css";
var themeName = context.Request["theme"];
var themeFileNames = context.Request["files"];
var version = context.Request["version"];
var isCompressed = CanGZip(context.Request);
var encoding = new UTF8Encoding(false);
if (WriteFromCache(context, themeName, version, isCompressed)) return;
using (var memoryStream = new MemoryStream(5000))
{
using (var writer = isCompressed ? (Stream)(new GZipStream(memoryStream, CompressionMode.Compress)) : memoryStream)
{
if (!string.IsNullOrEmpty(themeName))
{
var themeCssNames = themeFileNames.Split(',');
foreach (var fileName in themeCssNames)
{
var fileBytes = GetCss(context,
"~/App_Themes/" + themeName + "/" + fileName,
PrefixAllImagesWith, version, encoding);
writer.Write(fileBytes, 0, fileBytes.Length);
}
}
writer.Close();
}
var responseBytes = memoryStream.ToArray();
context.Cache.Insert(GetCacheKey(themeName, version, isCompressed),
responseBytes, null, System.Web.Caching.Cache.NoAbsoluteExpiration,
KeepInCache);
WriteBytes(responseBytes, context, isCompressed);
}
}
private static bool WriteFromCache(HttpContext context, string themeName,
string version, bool isCompressed)
{
var responseBytes = context.Cache[GetCacheKey(themeName,
version, isCompressed)] as byte[];
if (null == responseBytes) return false;
WriteBytes(responseBytes, context, isCompressed);
return true;
}
private static void WriteBytes(byte[] bytes, HttpContext context, bool isCompressed)
{
var response = context.Response;
response.AppendHeader("Content-Length", bytes.Length.ToString());
response.ContentType = "text/css";
if (isCompressed)
response.AppendHeader("Content-Encoding", "gzip");
context.Response.Cache.SetCacheability(HttpCacheability.Public);
context.Response.Cache.SetExpires(DateTime.Now.Add(KeepInCache));
context.Response.Cache.SetMaxAge(KeepInCache);
context.Response.Cache.AppendCacheExtension("must-revalidate, proxy-revalidate");
response.OutputStream.Write(bytes, 0, bytes.Length);
response.Flush();
}
private static bool CanGZip(HttpRequest request)
{
var acceptEncoding = request.Headers["Accept-Encoding"];
return !string.IsNullOrEmpty(acceptEncoding) &&
(acceptEncoding.Contains("gzip") || acceptEncoding.Contains("deflate"));
}
private static byte[] GetCss(HttpContext context, string virtualPath,
string imagePrefix, string version, Encoding encoding)
{
var physicalPath = context.Server.MapPath(virtualPath);
var fileContent = File.ReadAllText(physicalPath, encoding);
var imagePrefixUrl = imagePrefix +
VirtualPathUtility.GetDirectory(virtualPath).TrimStart('~').TrimStart('/');
var cssContent = UrlCheck.Replace(fileContent,
new MatchEvaluator(delegate(Match m)
{
var imgPath = m.Groups["path"].Value.TrimStart('\'').TrimEnd('\'').TrimStart('"').TrimEnd('"');
if (!imgPath.StartsWith("http://"))
{
return "url('" + imagePrefixUrl
+ imgPath
+ (imgPath.IndexOf('?') > 0 ? "&version=" + version : "?version=" + version)
+ "')";
}
else
{
return "url('" + imgPath + "')";
}
}));
return encoding.GetBytes(cssContent);
}
private static string GetCacheKey(string themeName, string version, bool isCompressed)
{
return "CssHttpHandler." + themeName + "." + version + "." + isCompressed;
}
public bool IsReusable
{
get
{
return true;
}
}
}
Then in the Render Method of the Page you are loading e.g. Default.aspx simply use this code
var themeName = Page.Theme;
if (string.IsNullOrEmpty(themeName)) return;
var linksToRemove = new List();
foreach (Control c in Page.Header.Controls)
if (c is HtmlLink)
if ((c as HtmlLink).Href.Contains("App_Themes/" + themeName))
linksToRemove.Add(c as HtmlLink);
string themeCssNames = "";
linksToRemove.ForEach(delegate(HtmlLink link)
{
Page.Header.Controls.Remove(link);
themeCssNames += VirtualPathUtility.GetFileName(link.Href) + ',';
});
var linkTag = new Literal();
var cssPath = CSS_PREFIX + "CssHttpHandler.ashx?theme=" + themeName
+ "&files=" + HttpUtility.UrlEncode(themeCssNames.TrimEnd(','))
+ "&version=" + CSS_VERSION;
linkTag.Text = string.Format(@"<link href=""{0}"" type=""text/css"" rel=""stylesheet"" /%gt", cssPath);
Page.Header.Controls.Add(linkTag);
Hope this Helps,
- Tim