OutputCache for UnAuthenticated Requests Asp.Net Mvc

I was writing some code for our production site as it was needing some scaling additions. I wanted to cache some key pages that for the most part have static content, but they have those clever headers that change based on your authentication (Home page, About page, etc).

The clever guys over at StackOverflow mentioned that they do output caching for everything as long as the user isn’t authenticated. So I was like “Genius, I will use output cache.” After some intense googling I basically found that you would have to use VaryByParam and pass the username. No bueno. This would make cached pages for all of our users on every page, and I would feel dirty inside knowing I solved the solution poorly. Then I saw some examples using custom attributes. Better I say to myself, but I like all the neat little options that the OutputCache currently has.

Solution

Here is the full solution, and I will explain how I came to it afterwards.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.UI;

namespace Cyberkruz
{
    /// <summary>
    /// Custom cache attribute that excludes unauthenticated
    /// users from the cache.
    /// </summary>
    public class UnAuthenticatedCacheAttribute : OutputCacheAttribute
    {
        private OutputCacheLocation? originalLocation;

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            if (filterContext.HttpContext.User.Identity.IsAuthenticated)
            {
                // required fix because attributes are cached
                // so if you hit a page unauthenticated it will
                // remain unauthenticated
                originalLocation = originalLocation ?? Location;

                // it's crucial not to cache Authenticated content
                Location = OutputCacheLocation.None;
            }
            else
            {
                Location = originalLocation ?? Location;
            }

            // set a filter callback. Also required because of
            // the caching content.
            filterContext.HttpContext.Response.Cache
                .AddValidationCallback(OnlyIfAnonymous, null);

            base.OnActionExecuting(filterContext);
        }

        /// <summary>
        /// Filter method for detecting anonymous users.
        /// </summary>
        /// <param name="httpContext">The HTTP context.</param>
        /// <param name="data">The data.</param>
        /// <param name="status">The status.</param>
        public void OnlyIfAnonymous(HttpContext httpContext,
            object data, ref HttpValidationStatus status)
        {
            if (httpContext.User.Identity.IsAuthenticated)
                status = HttpValidationStatus.IgnoreThisRequest;
            else
                status = HttpValidationStatus.Valid;
        }
    }
}

I first read an article http://dotnet.learningtree.com/2012/05/04/improving-performance-with-output-caching/ that has a pretty decent solution of creating a custom attribute that utilizes the validation callback. After implementation I realized an annoying problem. Cache attributes are in themselves cached. This means that if you hit the attribute and you are authenticated then you will end up never caching the page. I then found http://stackoverflow.com/questions/2109928/how-to-turn-output-caching-off-for-authenticated-users-in-asp-net-mvc that comes to the same conclusion as the first link. Midway down, however, there is a little gem that only has two upvotes where a user pointed out this issue and provides a solution.

I hope this helps.