ASP.NET 4.0: Writing custom output cache providers
Until now we can use ASP.NET default output cache. We don’t have any option to write our own output cache mechanisms. ASP.NET 4.0 introduces extensible output cache – programmers can write their own output cache providers. In this posting I will show you how to write your own output cache provider and give you my sample solution to download.
Preparing
Before we begin with caching we need Visual Studio 2010 solution that has two projects – one web application and one class library. Class library is for output cache provider and web application is for testing purposes. My solution uses following names:
- Solution: CacheProviderDemo
- Web application: CacheProviderApp
- Class library: MyCacheProviders
Before moving on let’s modify web application and add project reference to class library. In this case we don’t have to copy our cache provider to GAC after each build.
Configuring output caching
Now let’s change our web.config and add there the following configuration block under system.web section.
<caching>
<outputCache defaultProvider="AspNetInternalProvider">
<providers>
<add name="FileCache"
type="MyCacheProviders.FileCacheProvider, MyCacheProviders"/>
</providers>
</outputCache>
</caching>
This configuration block tells ASP.NET that there is output cache provider called FileCache available but we want to use default provider calles AspNetInternalProvider. We will change this block later.
Writing output cache provider
Now let’s start with output cache provider. From output cache configuration block you can see that Class1.cs in our class library must be renamed to FileCacheProvider.cs. Don’t forget also change the class name. Add reference to System.Web to class library and open FileCacheProvider.cs. In the beginning of this file add using line for System.Web.Cachingnamespace.
Our output cache provider class must extend OutputCacheProvider base class. This base class defines all methods that output cache providers must implement. Our output cache provider uses file system for cache storage. Output cache entries are serialized to binary format and back when writing and reading objects from files.
Classes for our custom output cache provider are here.
public class FileCacheProvider : OutputCacheProvider
{
private string _cachePath;
private string CachePath
{
get
{
if (!string.IsNullOrEmpty(_cachePath))
return _cachePath;
_cachePath = ConfigurationManager.AppSettings["OutputCachePath"];
var context = HttpContext.Current;
if (context != null)
{
_cachePath = context.Server.MapPath(_cachePath);
if (!_cachePath.EndsWith("\\"))
_cachePath += "\\";
}
return _cachePath;
}
}
public override object Add(string key, object entry, DateTime utcExpiry)
{
Debug.WriteLine("Cache.Add(" + key + ", " + entry + ", " + utcExpiry + ")");
var path = GetPathFromKey(key);
if (File.Exists(path))
return entry;
using (var file = File.OpenWrite(path))
{
var item = new CacheItem { Expires = utcExpiry, Item = entry };
var formatter = new BinaryFormatter();
formatter.Serialize(file, item);
}
return entry;
}
public override object Get(string key)
{
Debug.WriteLine("Cache.Get(" + key + ")");
var path = GetPathFromKey(key);
if (!File.Exists(path))
return null;
CacheItem item = null;
using (var file = File.OpenRead(path))
{
var formatter = new BinaryFormatter();
item = (CacheItem)formatter.Deserialize(file);
}
if (item == null || item.Expires <= DateTime.Now.ToUniversalTime())
{
Remove(key);
return null;
}
return item.Item;
}
public override void Remove(string key)
{
Debug.WriteLine("Cache.Remove(" + key + ")");
var path = GetPathFromKey(key);
if (File.Exists(path))
File.Delete(path);
}
public override void Set(string key, object entry, DateTime utcExpiry)
{
Debug.WriteLine("Cache.Set(" + key + ", " + entry + ", " + utcExpiry + ")");
var item = new CacheItem { Expires = utcExpiry, Item = entry };
var path = GetPathFromKey(key);
using (var file = File.OpenWrite(path))
{
var formatter = new BinaryFormatter();
formatter.Serialize(file, item);
}
}
private string GetPathFromKey(string key)
{
return CachePath + MD5(key) + ".txt";
}
private string MD5(string s)
{
var provider = new MD5CryptoServiceProvider();
var bytes = Encoding.UTF8.GetBytes(s);
var builder = new StringBuilder();
bytes = provider.ComputeHash(bytes);
foreach (var b in bytes)
builder.Append(b.ToString("x2").ToLower());
return builder.ToString();
}
}
With cached item we have to keep date and time that say us when cached object expires. I write simple CacheItem class that is marked as serializable (otherwise it is not possible to serialize it and therefore we cannot use binary formatter). CacheItem class is simple.
[Serializable]
internal class CacheItem
{
public DateTime Expires;
public object Item;
}
I made this class internal because we have no reason to show it to the world. We only use this class to store and restore cache entries with expiry date.
Some notes for FileCacheProvider class.
- You must have appSetting called OutputCachePath that contains path to cache files folder. This path may contain ~ because in the case of web application this path goes through Server.MapPath() method.
- File names are made of MD5 hashed cache entry key and .txt extension. You can use whatever file extension you like.
- I am using binary formatter because it workes better for me than XmlSerializer (Add() method is used with internal classes that have no public constructor with empty argument list).
- My code uses calls to Debug.WriteLine() method. When you run web application with Visual Studio then you can see output cache messages in output window. I found that it is very easy and powerful way to debug provider because these messages show you what is going on.
Preparing web application for testing
Let’s prepare now web application for testing. As a first thing we must make some changes to web.config. We have to add appSetting for cache folder and we have to make our file cache provider as default output cache provider. Here are XML-blocks for configuration.
<appSettings>
<add key="OutputCachePath" value="~/Cache/" />
</appSettings>
<caching>
<outputCache defaultProvider="FileCache">
<providers>
<add name="FileCache" type="MyCacheProvider.FileCacheProvider, MyCacheProvider"/>
</providers>
</outputCache>
</caching>
I expect you know where to put these blocks in web.config file. We have one more change to make before running our web application. Let’s turn output caching on for it. Although there should be providerName attribute available it doesn’t work yet. At least for my installation of Visual Studio 2010 and ASP.NET 4.0. Add the following line to Default.aspx.
<%@ OutputCache VaryByParam="ID" Duration="300" %>
I added VaryByParam for one reason: to test caching with different ID-s. Cache duration is 300 seconds (5 minutes). You can change to attributes if you like.
Testing output cache provider
Now let’s test our output cache provider. Run web application.
When you see default page in your browser check out output window in Visual Studio 2010. You should see there something like this.
Cache.Get(a2/default.aspx)
Cache.Add(a2/default.aspx, System.Web.Caching.CachedVary, 31.12.9999 23:59:59)
Cache.Set(a2/default.aspxHQNidV+n+FCDE, System.Web.Caching.OutputCacheEntry, 19.11.2009 12:20:56)
After refresh you should see something like this in output window.
Cache.Get(a2/default.aspx)
Cache.Get(a2/default.aspxHQNidV+n+FCDE)
Cache.Get(a2/styles/site.css)
Cache.Get(a2/webresource.axd)
Cache.Set(a2/styles/site.css, System.Web.Caching.OutputCacheEntry, 20.11.2009 12:17:09)
Cache.Add(a2/webresource.axd, System.Web.Caching.CachedVary, 31.12.9999 23:59:59)
Cache.Set(a2/webresource.axdHQNdVjnaEMMY-tPD2-oCbZFp7vHhVoQiB8SS98KHQMNKjUfs1FCDE, System.Web.Caching.OutputCacheEntry, 19.11.2010 12:17:09)
Cache.Get(a2/webresource.axd)
Cache.Get(a2/webresource.axdHQNdVqx_c6oOlTqymCAsGZh_8JA2FCDE)
Cache.Add(a2/webresource.axd, System.Web.Caching.CachedVary, 31.12.9999 23:59:59)
Cache.Set(a2/webresource.axdHQNdVqx_c6oOlTqymCAsGZh_8JA2FCDE, System.Web.Caching.OutputCacheEntry, 19.11.2010 12:17:10)
You see that second request caches some more data. There requests to FileCacheProvider are made by ASP.NET. During additional refresh some resources get cached and the other resources are removed from output cache.
Here is one simple screenshot fragment of file cache folder.
You can open these files to see what is inside them. You should see there cache items in binary format as binary formatter serializes them.
Conclusion
Although there are not much informative and guiding documentation about custom output cache providers I got mine working seemlessly well. I am not sure about some aspects of my provider and I will consult with Microsoft about my doubts. Of course, I will let you know if I get some valuable feedback from Microsoft.
The point of this posting was to show you how to write custom output cache provider. As we saw it is not something crucial or complex – we have to extend OutputCacheProvider class and implement some methods that store, edit and clear cached items. You can write your own implementations of output cache provider and keep your output cache wherever you like.
I am trying to create Outputcacheprovider and for some reason when I return the object back from Get it throws that the object (whatever type) cannot be converted to System.Web.Caching.CachedRawResponse.
Any ideas?
This will not handle the situations when the varybyid is from querystring.
Well… for caching you will get the cache object that ASP.NET gives you. It creates it and when needed it gives it to your code. You have to take this object and save it. If this object is asked then you have to return it. If you start your projects in debugging mode and put break point on the first line of some of these methods then you can check out the type of object it gives you.
Take a look at this class:
[Serializable]
internal class CacheItem
{
public DateTime Expires;
public object Item;
}
And check out how it used in code. Specially look at attribute called Item. It is never casted to any type. It is evaluated and returned but never casted.
If you have still problems with your code then please post it somewhere (or even here) so I can see it.
I just wanted to point out that if your application is running in a multi-server environment or even a web garden, ASP.NET Cache will not work because it is a stand-alone InProc cache. What you need is a distributed cache to allow you to scale up your application. NCache is an in-memory distributed cache for .NET. It has a very similar API to ASP.NET so migration to NCache is very easy. Check it out at
http://www.alachisoft.com/ncache/index.html
I want to point out that there are significant errors in this code. Specifically, the Add method is incorrectly implemented in two ways.
First, when it checks to see if the entry already exists, it does not check to see if an existing entry has expired. If it has expired, it must be replaced with the new entry and new expiration time.
Secondly, the Add method is clearly defined as returning the CACHED entry if the key is already in the cache, not the entry for which the Add method was called. The implementation in this article does not do that.
Thank you for your valuable feedback, .NETMaster. This may be key to solution of some other problems I ran into when dealing with caching.
Thankx .NETmaster, this is the key issue.
The code in Add method should be as you pointed, otherwise this provider does not work at all:
public override object Add(string key, object entry, DateTime utcExpiry)
{
var path = GetPathFromKey(key);
if (File.Exists(path))
{
object res = Get(key);
if (res == null)
return entry;
return res;
}
using (var file = File.OpenWrite(path))
{
var item = new CacheItem { Expires = utcExpiry, Item = entry };
var formatter = new BinaryFormatter();
formatter.Serialize(file, item);
}
return entry;
}
this method not work!
sorry, my mistake it is working
Excellent tutorial/example and followup corrections – thanks!
Hello,
Thank you for the article. I have used your provider but only the Get is called. Add or Set is never called. I have a Response.Filter set. Is this likely to be the reason? If it should work with this is the data cached pre or post the Response.Filter processing?
Thanks
Thanks for question, Neil! My implementation has some little bugs in it that I have to fix. As soon as I have time enough I will add correct implementation of output cache provider to my GitHub repository (http://bit.ly/gunhub)
For VaryByParam is not working. if you change the querystring only the first page is cached.
Hi Gunnar Peipman,
Awesome code… wanted to know if this is thread safe, if there are multiple requests to the cache, since the files are read, will there not be a access denied error / exception
Thanks & Regards
Mujahid
Hello
The keys that the Add, Get, Set, Remove methods receive in my implementaion are Hash values. How is it possible to receive the file path as a key?
Regards
Ioannis Dontas
Hi,
I implemented this caching solution in an ASP.MVC 2 Project, but unfortunately I get this error: “When using a custom output cache provider like ‘FileCache’, only the following expiration policies and cache features are supported: file dependencies, absolute expirations, static validation callbacks and static substitution callbacks.”
The way I’m using this is :
[OutputCache(Duration = 2592000, VaryByParam = “posttags”, VaryByCustom=”user”)]
public ActionResult Index(string posttags)
Can you tell me please what I’m doing wrong?
Thanks
Sorry to dig up the dead, but we’ve been working with OutputCacheProviders recently and have noticed that the utcExpiry for Add is always 12/31/9999 23:59:59… Set respects the value we have configured in the OutputCache duration attribute in the annotation.
https://coin24.io/ provides excellent services for Polish traders. I found their cryptocurrency exchange process simple and efficient. Fast transactions – exactly what I was looking for!
Dubai’s economy supports businesses best business setup company in dubai the creative sector.
Please tell me more about your excellent articles http://www.kayswell.com
You’ve the most impressive websites. http://www.kayswell.com
I have to express thanks to you for bailing me out of this challenge. As a result of looking out through the the web and getting things that were not productive, I believed my entire life was done. Living without the strategies to the problems you’ve resolved through your posting is a serious case, and ones which could have adversely damaged my career if I had not encountered your site. Your own personal training and kindness in taking care of all the pieces was excellent. I don’t know what I would have done if I had not come across such a solution like this. I can also at this time relish my future. Thanks a lot very much for the expert and amazing help. I will not think twice to suggest your site to any individual who should receive care on this matter.
Thank you for sharing this article with me. It helped me a lot and I love it. http://www.hairstylesvip.com
These are actually great ideas in about blogging. You have touched some pleasant things here. Any way keep up wrinting.
Really when someone doesn’t be aware of afterward its up to other viewers that they will help, so here it occurs. http://www.kayswell.com
When I initially commented I clicked the “Notify me when new comments are added” checkbox and now each time a comment is added I get several emails with the same comment. Is there any way you can remove people from that service? Many thanks! http://www.kayswell.com
Hello, i read your blog occasionally and i own a similar one and i was just curious if you get a lot of spam feedback? If so how do you stop it, any plugin or anything you can recommend? I get so much lately it’s driving me insane so any help is very much appreciated. http://www.kayswell.com
Hurrah! In the end I got a weblog from where I know how to truly take helpful information regarding my study and knowledge. http://www.kayswell.com
Hello my loved one! I wish to say that this post is amazing, great written and include approximately all significant infos. I would like to peer extra posts like this . http://www.kayswell.com
Hello there, You’ve done a great job. I will definitely digg it and personally recommend to my friends. I am confident they will be benefited from this website. http://www.kayswell.com
Greetings! Very helpful advice in this particular article! It’s the little changes that will make the most significant changes. Thanks a lot for sharing! http://www.ifashionstyles.com
It’s hard to find educated people on this subject, but you sound like you understand what you’re speaking about! Thanks
Really when someone doesn’t be aware of afterward its up to other viewers that they will help, so here it occurs. http://www.hairstylesvip.com
An fascinating dialogue is price comment. I believe that you need to write more on this subject, it may not be a taboo topic however usually individuals are not enough to speak on such topics. To the next. Cheers
This is my first time visit at here and i am actually happy to read everthing at alone place. http://www.kayswell.com
I loved as much as you will receive carried out right here. The sketch is tasteful, your authored material stylish. nonetheless, you command get bought an edginess over that you wish be delivering the following. http://www.kayswell.com unwell unquestionably come further formerly again since exactly the same nearly very often inside case you shield this hike.
You should be a part of a contest for one of the finest blogs on the net. I’m going to highly recommend this blog! http://www.kayswell.com
Hi there, everything is going sound here and ofcourse every one is sharing data, that’s really good, keep up writing. http://www.kayswell.com
Wow, marvelous weblog structure! How long have you ever been running a blog for? you make running a blog glance easy. The overall look of your web site is wonderful, as smartly as the content material!
Hello There. I found your blog the use of msn. This is a really well written article. I will make sure to bookmark it and come back to read extra of your useful info. Thanks for the post.
Thanks for the sensible critique. Me & my neighbor were just preparing to do a little research about this. We got a grab a book from our local library but I think I learned more from this post. I am very glad to see such excellent info being shared freely out there.
Excellent post. I was checking constantly this blog and I’m impressed! Extremely helpful info specially the final part :) I care for such information much. I was seeking this certain information for a long time. Thanks and good luck.
What’s up i am kavin, its my first time to commenting anywhere, when i read this paragraph i thought i could also make comment due to this sensible post.
You actually make it seem so easy with your presentation but I find this matter to be actually something that I think I would never understand. It seems too complex and very broad for me.I’m looking forward for your next post, I will try to get the hang of it!
wonderful points altogether, you just received a new reader. What would you suggest about your put up that you simply made some days ago? Any positive?
Appreciation to my father who informed me on the topic of this web site, this website is really amazing.
It’s enormous that you are getting ideas from this paragraph as well as from our dialogue made at this time.
Hey there! Would you mind if I share your blog with my myspace group? There’s a lot of people that I think would really enjoy your content. Please let me know. Thanks
Pretty great post. I just stumbled upon your weblog and wanted to say that I have really loved browsing your weblog posts. After all I’ll be subscribing on your feed and I hope you write once more very soon!
This is my first time visit at here and i am actually happy to read everthing at alone place. http://www.kayswell.com
We stumbled over here coming from a different web page and thought I might check things out. I like what I see so now i’m following you.Look forward to finding out about your web page yet again. http://www.kayswell.com
If you want to increase your experience just keep visiting this site and be updated with the most up-to-date news update posted here. http://www.kayswell.com
This is my first time pay a visit at here and i am really pleassant to read all at single place.
Thanks for your help and for writing this post. It’s been great. http://www.kayswell.com
Hi, i think that i saw you visited my weblog so i came to “return the favor”.I am trying to find things to enhance my site!I suppose its ok to use some of your ideas!! http://www.kayswell.com
These are actually great ideas in about blogging. You have touched some pleasant things here. Any way keep up wrinting.
Hi, i think that i saw you visited my weblog so i came to “return the favor”.I am trying to find things to enhance my site!I suppose its ok to use some of your ideas!!