Making Cache Aware ResolveUrl/Url.Content
Introduction:
Caching static contents is great and recommended but in the same time we need to also consider the fact that the static contents can be updated any time. A popular way to handle this scenario is to change the contents path or append a query-string in contents url. But doing this manually each time whenever you update the file is not very easy. Recently, I faced the same issue on an application which was using caching heavily for static contents. I was able to automate the process of appending query-string in static contents url in an efficient way. In this article, I will show you how I was able to automate the process by creating cache aware ResolveUrl and Url.Content methods.
Description:
Before using and testing the cache aware ResolveUrl and Url.Content methods, I will recommend to configure IIS to add cache for static contents. Next just add these cache aware ResolveUrl and Url.Content extension methods in your application,
public static class UrlHelperExtensions { public static string CacheAwareContent(this UrlHelper url, string contentPath, bool useLastModifiedDate = true) { var path = url.Content(contentPath); var context = url.RequestContext.HttpContext; var physicalPath = context.Server.MapPath(contentPath); string v; if (context.Cache[physicalPath] == null) { if (useLastModifiedDate) v = GetFileLastModifiedDate(context, physicalPath); else v = GetMD5FileHash(context, physicalPath); context.Cache.Insert(physicalPath, v, new CacheDependency(physicalPath)); } else { v = context.Cache[physicalPath].ToString(); } return path + "?v=" + v; } private static string GetMD5FileHash(HttpContextBase context, string physicalPath) { byte[] hash = MD5.Create().ComputeHash(File.ReadAllBytes(physicalPath)); return BitConverter.ToString(hash).Replace("-", ""); } private static string GetFileLastModifiedDate(HttpContextBase context, string physicalPath) { return new FileInfo(physicalPath).LastWriteTime.ToString("yyyyMMddhhmmss"); } } public static class PageExtensions { public static string CacheAwareResolveUrl(this Page page, string relativeUrl, bool useLastModifiedDate = true) { var path = page.ResolveUrl(relativeUrl); var context = page.ModelBindingExecutionContext.HttpContext; var physicalPath = context.Server.MapPath(relativeUrl); string v; if (context.Cache[physicalPath] == null) { if (useLastModifiedDate) v = GetFileLastModifiedDate(context, physicalPath); else v = GetMD5FileHash(context, physicalPath); context.Cache.Insert(physicalPath, v, new CacheDependency(physicalPath)); } else { v = context.Cache[physicalPath].ToString(); } return path + "?v=" + v; } private static string GetMD5FileHash(HttpContextBase context, string physicalPath) { byte[] hash = MD5.Create().ComputeHash(File.ReadAllBytes(physicalPath)); return BitConverter.ToString(hash).Replace("-", ""); } private static string GetFileLastModifiedDate(HttpContextBase context, string physicalPath) { return new FileInfo(physicalPath).LastWriteTime.ToString("yyyyMMddhhmmss"); } }
In the above code, I have added CacheAwareResolveUrl and CacheAwareContent extension methods. These methods are same as their ResolveUrl and Url.Content counterparts except that these methods append a new v query-string when required.
When required? Means whenever the static resource referred in the url change.
What we do when change happen? We will append resource's last modified date(or resource file hash) in the v query-string.
There will be a performance hit for every call of CacheAwareResolveUrl and CacheAwareContent? No because these extension methods leverages ASP.NET cache object.
Then how ASP.NET cache knows about the static resource change? Simple, using file dependency option of cache.
Now you can easily use these extension methods in your page(or view), utilizing full static resource caching without fearing about the file change,
@Url.CacheAwareContent("~/Content/Site.css") <%= this.CacheAwareResolveUrl("~/Content/Site.css") %>
Optionally you can pass false in the above methods to use file-hash in query-string instead of last modified date. Make sure to import the namespace in your web page(or view) before using these methods.
Summary:
In this article, I showed you how simply we can create cache aware ResolveUrl and Url.Content methods. Using these methods we can utilize full static caching without fearing about file update. Hopefully you will enjoy my this article too.