Reputation: 13356
I have a menu on my site that changes depending on whether the user is logged in or not. With browser caching, the menu "gets stuck" in either state and is confusing to users.
They'll login, but the menu won't update because it's still cached in the unauthenticated state... and vice versa.
How is this typically handled? Can we refresh the user's browser cache from our code? Or do I just not allow browser caching? (would rather use it, very nice bump in speed).
Update
Here's how I set client-side, browser caching in my asp.net mvc 2 app:
public class CacheFilterAttribute : ActionFilterAttribute {
/// <summary>
/// Gets or sets the cache duration in seconds. The default is 10 seconds.
/// </summary>
/// <value>The cache duration in seconds.</value>
public int Duration { get; set; }
public CacheFilterAttribute() { Duration = 10; }
public override void OnActionExecuted(ActionExecutedContext filterContext) {
if (Duration <= 0) return;
var cache = filterContext.HttpContext.Response.Cache;
var cacheDuration = TimeSpan.FromSeconds(Duration);
cache.SetCacheability(HttpCacheability.Public);
cache.SetExpires(DateTime.Now.Add(cacheDuration));
cache.SetMaxAge(cacheDuration);
cache.AppendCacheExtension("must-revalidate, proxy-revalidate");
}
}
And then apply a [CachFilter(Duration = 60)] to my actions. (note, I got the code above from Kazi Manzur Rashid's Blog
Upvotes: 0
Views: 958
Reputation: 46
I've been playing around a bit with an action filter to simulate that behavior using conditional requests and e-tags, it's pure client side caching so no output caching involved here. You still get a request to the server but the action will not be called and the client will use the local cache if it's still fresh.
/// <summary>
/// Handles client side caching by automatically refreshing content when switching logged in identity
/// </summary>
public class ClientCacheByIdentityAttribute : ActionFilterAttribute
{
/// <summary>
/// Sets the cache duraction in minutes
/// </summary>
public int Duration { get; set; }
/// <summary>
/// Check for incoming conditional requests
/// </summary>
/// <param name="filterContext"></param>
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.IsChildAction || filterContext.HttpContext.Request.RequestType!="GET" || filterContext.Result!=null)
{
return;
}
string modifiedSinceString = filterContext.HttpContext.Request.Headers["If-Modified-Since"];
string noneMatchString = filterContext.HttpContext.Request.Headers["If-None-Match"];
if (String.IsNullOrEmpty(modifiedSinceString) || String.IsNullOrEmpty(noneMatchString))
{
return;
}
DateTime modifiedSince;
if (!DateTime.TryParse(modifiedSinceString, out modifiedSince))
{
return;
}
if (modifiedSince.AddMinutes(Duration) < DateTime.Now)
{
return;
}
string etag = CreateETag(filterContext.HttpContext);
if (etag == noneMatchString)
{
filterContext.HttpContext.Response.StatusCode = 304;
filterContext.Result = new EmptyResult();
}
}
/// <summary>
/// Handles setting the caching attributes required for conditional gets
/// </summary>
/// <param name="filterContext"></param>
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext.HttpContext.Request.RequestType == "GET" && filterContext.HttpContext.Response.StatusCode == 200 && !filterContext.IsChildAction && !filterContext.HttpContext.Response.IsRequestBeingRedirected)
{
filterContext.HttpContext.Response.AddHeader("Last-Modified", DateTime.Now.ToString("r"));
filterContext.HttpContext.Response.AddHeader("ETag", CreateETag(filterContext.HttpContext));
}
}
/// <summary>
/// Construct the ETag
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private static string CreateETag(HttpContextBase context)
{
return "\"" + CalculateMD5Hash(context.Request.Url.PathAndQuery + "$" + context.User.Identity.Name) + "\"";
}
/// <summary>
/// Helper to make an MD5 hash
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
private static string CalculateMD5Hash(string input)
{
MD5 md5 = MD5.Create();
byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(input);
byte[] hash = md5.ComputeHash(inputBytes);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < hash.Length; i++)
{
sb.Append(hash[i].ToString("X2"));
}
return sb.ToString();
}
}
Upvotes: 2
Reputation: 532445
You might want to try donut hole caching and cache everything except the menu. Scott Guthrie has a nice article on the topic.
Upvotes: 1