I added the new build of MvcContrib to OpenIdPortableArea today, and committed the new embedded resource routes.  It worked great, and I’m working on some other things with the portable area.

I noticed that the newly embedded images started displaying slightly slower than before.  Maybe it’s all in my head, but it got me thinking about caching nonetheless.  The embedded JavaScript, images, and CSS files are never going to change.  As implemented right now, each request for embedded content requires that the mime type be determined, then locate the resource in the assembly, and then returns a FilePathResult() from the stream of the extracted resource.  With caching, we can prevent this operation from happening on every request.

After thinking of this, I quickly pitched it to Steve Michelotti.  I suggested the possiblity of including [OutputCache(CacheProfile="EmbeddedResource")] on the EmbeddedResourceController’s Index action.  He thought this was an interesting approach, because it leaves the actual cache settings up to the programmer using this particular feature.

So, as with any good idea I gave it a shot.  I found that it worked great, but there was a catch.  Using the OutputCache.CacheProfile property means you absolutely must provide that cache profile in the web.config.  You get a yellow screen of death without it.  With this in mind, adding such a cache profile requirement is just one more mandatory configuration to remember.  How can we make it just work.  Maybe the programmer using this code has some insane reason not to cache these resources.  Whatever the case may be, it would be nice to allow the consumer to opt into the caching mechanism and allow everything to work without it.

First Idea

I quickly created a class called OptionalOutputCacheAttribute, which inherited directly from System.Web.Mvc.OutputCacheAttribute.  The OutputCacheAttribute class uses OnResultExecuting() to serve the cached version of the page.  My plan was to check and see if that particular cache profile exists, and bail if it doesn’t.  Sounds good in theory, but fails in practice.  Why, you say?  Because the OutputCacheAttribute.CacheSettings property is marked internal.  Crap.  So much for that idea.

EDIT: On second thought, this is probably not the real reason. The OutputCacheAttribute doesn't expose its internal OutputCacheParameters object, nor does it expose an Enabled property. It is more likely that ASP.NET deals with caching a little deeper than we can get to, and I suspect it just doesn't support the idea of "maybe cache...". If the OutputCache attribute exists, then cache it. Period.

Second Idea

I have a backup plan, because there just has to be a way to make this work.  Keeping the OptionalOutputCacheAttribute class, what else is there to work with?  I found a way to make it work, but some might find it… unsavory.

The attribute takes a moment and inspects the application’s web.config.  Here’s the code:

/// <summary>

///     Provides optional chaching

/// </summary>

public class OptionalOutputCacheAttribute : OutputCacheAttribute

{

    /// <summary>

    ///     Contains a record of what CacheProfiles have been looked for

    /// </summary>

    protected static Dictionary<string, bool> CacheProfiles { get; set; }

 

    /// <summary>

    ///     Constructor

    /// </summary>

    public OptionalOutputCacheAttribute()

    {

        CacheProfiles = new Dictionary<string, bool>();

    }

 

    /// <summary>

    ///     Determines if the target CacheProfile exists before trying

    ///     to handle the caching of the page.

    /// </summary>

    /// <param name="filterContext"></param>

    public override void OnResultExecuting(ResultExecutingContext filterContext)

    {

        if (!CacheProfiles.Keys.Contains(CacheProfile))

        {

            XDocument xdoc =

                XDocument.Load(AppDomain.CurrentDomain.BaseDirectory + "\\web.config");

 

            var profileExists =

                xdoc.Descendants("outputCacheProfiles")

                    .Descendants(XName.Get("add"))

                    .Any(node => node.Attribute(XName.Get("name")).Value == CacheProfile);

 

            CacheProfiles.Add(CacheProfile, profileExists);

        }

 

        if (CacheProfiles[CacheProfile])

            base.OnResultExecuting(filterContext);

    }

}


Using Linq to Xml, it’s fairly trivial to parse the web.config.  It loads perfectly into an XDocument.  Then, it’s easy to see if the “outputCacheProfiles” node has any “add” nodes with a “name” attribute equivalent to the name of the cache profile.

As a way of reducing the frequency of web.config parses, the attribute keeps a static Dictionary.  The first thing to check is if this particular cache profile has been looked up.  If it hasn’t, we need to load the web.config and do the search.  The result is added to the dictionary for later use.

After all of this parsing nonsense, we index into the dictionary and use the bool value.  If the dictionary says that the cache profile exists, we allow the base class (System.Web.Mvc.OutputCacheAttribute) to execute its OnResultExecuting() method.

The Outcome

Using this new OptionalOutputCacheAttribute, we can decorate the EmbeddedResourceController with it:

[OptionalOutputCache(CacheProfile="EmbeddedResource")]

public ActionResult Index(string resourceName, string resourcePath)

{

    // ....

}


My initial tests for this code have proved successful.  What does everyone else think of this?  I was a little iffy on the whole web.config parsing thing (still am).  However, I think this point of extensibility or configurability is more favorable than either A) not providing any support for caching embedded resources, or B) arbitrarily determining cache settings.

Thursday, April 15, 2010 12:48:35 AM (Eastern Daylight Time, UTC-04:00)  #    Comments [0] -
ASP.NET MVC | Caching | MvcContrib | OpenIdPortableArea

John Nelson

mugshot I am a passionate C# Developer working in ASP.NET on an e-commerce solution for ticketing software. I work across all of the application layers, including server side functionality, and client side programming with jQuery and MS Ajax. Although my full time job is in WebForms, I spend many of my off hours working with MVC. I am especially interested in productivity and good programming practices.

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright 2010
johncoder.com
Statistics
Total Posts: 41
This Year: 17
This Month: 0
This Week: 0
Comments: 4