Wednesday, June 24, 2009

OutputCacheAttribute Override/Customization

The scenario: A web app is using the OutputCacheAttribute to set a cache duration for data that has different life spans.

The IDE: ASP.NET MVC with Visual Studio 2008
The environment: Web-App on IIS

The data that is being retrieved varies greatly in terms of their lifespan:
  • Some data is very static like States, Countries
  • Other data is more fluid, especially during end-user testing, like the load of scripts that have to be fixed occasionally and pushed to production during the day. Especially early on in the test these pushes can happen hourly in some cases and as progress is made during the tests becomes less frequent
  • A list of FAQ and Bug fixes that the users can access from the web page has to be refreshed a few times a day

No matter the reason or validity thereof, it is simple enough to specify the OutputCache for each. For example:
Caches the load of script files for 2 hours:

[OutputCache(Duration = 7200, VaryByParam = "none")]
public FileResult GetjQueryScripts()
{
string combinedResults = combineLikeDirectoryFiles("~/Scripts/jquery/Base/", FileType.js);
combinedResults += combineLikeDirectoryFiles("~/Scripts/jquery/Plugins/", FileType.js);
combinedResults += combineLikeDirectoryFiles("~/Scripts/jquery/Plugins/jquery.grid/", FileType.js);
combinedResults += combineLikeDirectoryFiles("~/Scripts/jquery/Plugins/jquery.datepick/", FileType.js);

return streamText(combinedResults, js);
}

Caches States for 2 days:

[OutputCache(Duration = 57600, VaryByParam = "none")]
public string GetAllStates()
{
return GetAllForGenericData(Repository.FindAll());
}


The problem: This works great, but has some issues:
  • hard coded so needs re-compilation and code-migration (who knows how long that can take)
  • In a development environment, especially if you do not want caching. For example my scripts are cached and when I make a script change do not want the cached version locally during unit testing. Set the duration property of the cache to 0 for this and REMEMBER to change it to (what was it again?!) before you build and deploy, or commit/check-in to version-control.
The solution: Use the CacheProfile property from ASP.NET's MVC or create a custom output cache attribute that in this example takes two parameters, a file type (enum) and the VaryByParam. Unlike the CacheProfile option, this one can target any source, from databases to flat files, xml files, JSON files, etc. Here is what the method/action looks like when decorated with the custom attribute. It loads script files based on a file type (js, css, etc) and retrieves the duration from the web.config file (of course this can be anything, DB, xml file, etc):

[CustomOutputCacheAttribute(TypeOfFileToCache = FileType.js, VaryByParam = "none")]

public FileResult GetjQueryScripts()
{
string combinedResults = combineLikeDirectoryFiles("~/Scripts/jquery/Base/", FileType.js);
combinedResults += combineLikeDirectoryFiles("~/Scripts/jquery/Plugins/", FileType.js);
combinedResults += combineLikeDirectoryFiles("~/Scripts/jquery/Plugins/jquery.grid/", FileType.js);
combinedResults += combineLikeDirectoryFiles("~/Scripts/jquery/Plugins/jquery.datepick/", FileType.js);

return streamText(combinedResults, js);
}


Here is the declaration of the custom attribute:

public class CustomOutputCacheAttribute : OutputCacheAttribute
{
private FileType _typeOfFileToCache;

public override void OnActionExecuting(ActionExecutingContext filterContext)
{
SetupDuration();
base.OnActionExecuting(filterContext);
}

public FileType TypeOfFileToCache
{
get { return _typeOfFileToCache; }
set { _typeOfFileToCache = value; }
}

public string VaryByParam
{
get { return base.VaryByParam; }
set { base.VaryByParam = value; }
}

public void SetupDuration()
{
int durateMe;
switch (TypeOfFileToCache)
{
case FileType.js:
{
base.Duration = int.TryParse(ConfigurationSettings.AppSettings[ "outputCacheDurationScriptLoad" ],
out durateMe)
? durateMe
: 0;
}
break;
case FileType.css:
{
base.Duration = int.TryParse(ConfigurationSettings.AppSettings[ "outputCacheDurationCssLoad" ],
out durateMe)
? durateMe
: 0;
}
break;
default:
base.Duration = 0;
break;
}
}
}

public enum FileType
{
js = 1,
css = 2
}


Note that it inherits from System.MVC library's OutputCacheAttribute class. The OnActionExecuting method is called by the framework, which is overridden. The OnActionExecuting method essentially only has one addition, the SetupDuration() method that gets its duration from the web.config file based on the file type. In this example the app has a different duration for css and js files. The next important step in the OnActionExecuting() method is to invoke the base's OnActionExecuting() method, and you're done.

No comments:

Post a Comment