ASP.NET MVC OutputCache Vary by Current User

Caching user-specific data is a common requirement in web applications. Most times the first thing that you do when you sign in a user in your website is redirect them to a dashboard that is unique for that user. When the user spends a lot of time on his dashboard, you need to make sure that you can improve his experience without compromising your ability to fetch all the relevant data from all the different parts of your system.

In ASP.NET MVC, the easier way to cache the full rendered page is to use the OutputCacheAttribute, like so:

[Authorize]
public class DashboardController : Controller 
{
    [OutputCache(Duration = 3600)]
    public ActionResult Index() 
    {
       // Your awesome code goes here
    }
}

The previous code would make the Index action stay in cache for one hour. This works great for public content, but for user-specific content you must complement the cache key, so that different users don’t see each-other’s cache.

To do this, we use the VaryByCustom property of the OutputCacheAttribute, like this:

[Authorize]
public class DashboardController : Controller 
{
    [OutputCache(Duration = 3600, VaryByCustom = "User")]
    public ActionResult Index() 
    {
       // Your awesome code goes here
    }
}

And you also need to handle that “User” value in a method that is called over the HttpApplication: the GetVaryByCustomString method. So, you need to go to your Global.asax.cs and override that method:

public override string GetVaryByCustomString(HttpContext context, string arg) 
{ 
    if (arg.Equals("User", StringComparison.InvariantCultureIgnoreCase)) 
    {
        var user = context.User.Identity.Name; // TODO here you have to pick an unique identifier from the current user identity
        return string.Format("{0}@{1}", userIdentifier.Name, userIdentifier.Container); 
    }

    return base.GetVaryByCustomString(context, arg); 
}

Or, if you don’t set a custom principal in your pipeline, you can look for the session id like so:

private static SessionStateSection SessionStateSection = (System.Web.Configuration.SessionStateSection)ConfigurationManager.GetSection("system.web/sessionState");

public override string GetVaryByCustomString(HttpContext context, string arg) 
{ 
    if (arg.Equals("User", StringComparison.InvariantCultureIgnoreCase)) 
    {
        var cookieName =  SessionStateSection.CookieName;
        var cookie = context.Request.Cookies[cookieName];
        return cookie.Value;
    }

    return base.GetVaryByCustomString(context, arg); 
}

I’m using the session state configuration section to get the session cookie name, so that if you changed the default “ASP.NET_SessionId” it will still work.

I hope you will find this handy. If you have more cache subjects you would like me to write about, please write in the comments. In the next post I expect to demonstrate how to implement an OutputCacheAttribute that that enables you to choose another cache provider instead of the memory cache, so that you can use a distributed cache system like AppFabric or a Redis Cluster to store your rendered pages.