To speed up a dynamic website that I maintain, I decided to use the output caching mechanism built into ASP.NET. I wanted to bypass the page lifecycle and database roundtrips to achieve a faster loading webpage. However I was faced with a couple problems which are worth noting.
Memory is more expensive than disk space so it was important that I cache the webpages to disk rather than in-memory. I therefore hook up a custom output cache provider by leveraging the features of ASP.NET 4.0 and tailored it for disk caching. This worked fine for the caching portion but I also needed to evict certain pages based on user actions.
Deleting output cache for a single page based on QueryString
Delete the cache version of a page like “Product.aspx” is easy but if you wanted to evict “Product.aspx?id=4″ from the cache, then there’s no easy solution. There is only one method to delete cache : HttpResponse.RemoveOutputCacheItem which takes a virtual absolute path to work. When I looked at the code through reflection, I saw that this method was taking in parameters like the path to the webpage, the HttpVerb (GET or POST), the VaryBy parameters to generate a cache key for that particular webpage. To evict an item from the cache programmatically, you would need to know the cache key and it’s actually difficult to recompute the key without making your own method which will be an overkill.
To overcome the problem, I changed the custom cache provider and had a static RemoveItemFromCache method which takes in a cached key and deletes the file. I analysed the cached key that was being generated (eg a2/product.aspxHQNidV4FCDE) and realised that after HQN it was the QueryString parameter that was inserted and before FCDE it was the value of the QueryString. I could therefore delete a particular page by entering the cache key. Note that when the cached files are saved to disk, the “/” in the file path are replaced with an underscore “_”.
Session is not accessible when output caching is enabled
Full page caching is better than caching multiple parts of the webpage through user controls as you will see a better performance with the former. However I needed to have login links for anonymous users (users which are not logged in yet) and logout links for those who are currently signed in. There’s a substitution control which you can use for that matter :
<asp:Substitution ID=”MySubstitutionControl” MethodName=”" runat=”server” />
The MethodName that you specify will return a string which will be inserted in the cached page. This is known as donut caching as you’re caching the whole page but have holes in it (placeholders) where you can insert dynamic text. You can also use Response.WriteSubstitution in your code behind if you don’t like the idea of declarative syntax.
The problem with the substitution control however is that the Session object is not accessible. I was storing the username of the currently logged in user in the session but with the output caching, I was not able to retrieve this info from the session as it was always null. It makes sense though because when using output caching you’re bypassing the asp.net page life cycle and the session object is not populated and the page is rather retrieved from cache. For me, it was important to display the username with a logout link though and I had to find a solution to it.
My workaround was to use a cookie to store the username when people sign in. Since the cookies collection is available (as well as the request object), I was able to display the logout link with the username properly.
AdRotator does not do post cache substitution when the AdCreated method has been overridden
The ASP.NET team says that the AdRotator control has built in post cache substitution and while this is true, you will encounter problems if you override the AdCreated Event. By doing this, you’re losing the post cache features and you’ll notice that an advertisement banner that was shown on the first view of a page before it is cached will be shown on subsequent views of that page because substitution will not occur. To fix the problem, you’ll have to come with your own post caching mechanism for this control or build your own custom control to display dynamic banners which is not a tough task.
Server.Transfer will not cache out page response
On one particular page, I was using a Default.aspx page in the root to transfer to another page where the logic is run. This is because I wanted a particular folder to display the same content (varied by querystring) from a different page. The server.transfer method worked fine except when I enabled caching on the transferred to page. So if Page A is transferred to Page B and Page B has the output cache, you’ll find that Page A won’t be output cached.
The solution to this problem is to use Server.Execute instead of Server.Transfer and Page A will be cached.
Programmatically using output cache in code behind
I found that using Response.Cache with multiple VaryByParam parameters does not work well. So if you use this:
Response.Cache.SetExpires(DateTime.Now.AddSeconds(36000)); Response.Cache.SetCacheability(HttpCacheability.Server); Response.Cache.VaryByParams["p"] = true; Response.Cache.VaryByParams["s"] = true; Response.Cache.VaryByParams["t"] = true; Response.Cache.SetVaryByCustom("RawUrl"); Response.Cache.SetValidUntilExpires(true);
You’ll have to use this instead :
Response.Cache.SetExpires(DateTime.Now.AddSeconds(36000)); Response.Cache.SetCacheability(HttpCacheability.Server); Response.Cache.VaryByParams["*"] = true; Response.Cache.SetVaryByCustom("RawUrl"); Response.Cache.SetValidUntilExpires(true);
The wildcard (*) will cache your pages properly.