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,
01 |
public
static
class
UrlHelperExtensions
|
02 |
{
|
03 |
public
static
string
CacheAwareContent(this
UrlHelper url, string
contentPath, bool
useLastModifiedDate = true)
|
04 |
{
|
05 |
var path = url.Content(contentPath);
|
06 |
var context =
url.RequestContext.HttpContext;
|
07 |
var physicalPath =
context.Server.MapPath(contentPath);
|
08 |
string
v;
|
09 |
if
(context.Cache[physicalPath] == null)
|
10 |
{
|
11 |
if
(useLastModifiedDate)
|
12 |
v = GetFileLastModifiedDate(context,
physicalPath);
|
13 |
else
|
14 |
v = GetMD5FileHash(context,
physicalPath);
|
15 |
context.Cache.Insert(physicalPath, v, new
CacheDependency(physicalPath));
|
16 |
}
|
17 |
else
|
18 |
{
|
19 |
v =
context.Cache[physicalPath].ToString();
|
20 |
}
|
21 |
return
path + "?v="
+ v;
|
22 |
}
|
23 |
private
static
string
GetMD5FileHash(HttpContextBase context, string
physicalPath)
|
24 |
{
|
25 |
byte[] hash =
MD5.Create().ComputeHash(File.ReadAllBytes(physicalPath));
|
26 |
return
BitConverter.ToString(hash).Replace("-", "");
|
27 |
}
|
28 |
private
static
string
GetFileLastModifiedDate(HttpContextBase
context, string
physicalPath)
|
29 |
{
|
30 |
return
new
FileInfo(physicalPath).LastWriteTime.ToString("yyyyMMddhhmmss");
|
31 |
}
|
32 |
}
|
33 |
public
static
class
PageExtensions
|
34 |
{
|
35 |
public
static
string
CacheAwareResolveUrl(this
Page page, string
relativeUrl, bool
useLastModifiedDate = true)
|
36 |
{
|
37 |
var path =
page.ResolveUrl(relativeUrl);
|
38 |
var context =
page.ModelBindingExecutionContext.HttpContext;
|
39 |
var physicalPath =
context.Server.MapPath(relativeUrl);
|
40 |
string
v;
|
41 |
if
(context.Cache[physicalPath] == null)
|
42 |
{
|
43 |
if
(useLastModifiedDate)
|
44 |
v = GetFileLastModifiedDate(context,
physicalPath);
|
45 |
else
|
46 |
v = GetMD5FileHash(context,
physicalPath);
|
47 |
context.Cache.Insert(physicalPath, v, new
CacheDependency(physicalPath));
|
48 |
}
|
49 |
else
|
50 |
{
|
51 |
v =
context.Cache[physicalPath].ToString();
|
52 |
}
|
53 |
return
path + "?v="
+ v;
|
54 |
}
|
55 |
private
static
string
GetMD5FileHash(HttpContextBase context, string
physicalPath)
|
56 |
{
|
57 |
byte[] hash =
MD5.Create().ComputeHash(File.ReadAllBytes(physicalPath));
|
58 |
return
BitConverter.ToString(hash).Replace("-", "");
|
59 |
}
|
60 |
private
static
string
GetFileLastModifiedDate(HttpContextBase
context, string
physicalPath)
|
61 |
{
|
62 |
return
new
FileInfo(physicalPath).LastWriteTime.ToString("yyyyMMddhhmmss");
|
63 |
}
|
64 |
}
|
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,
1 |
@Url.CacheAwareContent("~/Content/Site.css")
|
2 |
<%=
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.