I have an action method that I want to cache:
[OutputCache(Duration=60*5, Location=OutputCacheLocation.Any, VaryByCustom="index")]
public ActionResult Index()
{
return View();
}
With this approach:
public override string GetVaryByCustomString(HttpContext context, string custom)
{
context.Response.Cache.SetOmitVaryStar(true);
context.Response.Cache.VaryByHeaders["Cookie"] = true;
if (User.Identity.IsAuthenticated)
{
Debug.Print("Authenticated");
context.Response.Cache.SetNoServerCaching();
context.Response.Cache.SetCacheability(HttpCacheability.Private);
return null;
}
else
{
Debug.Print("Non authenticated");
return custom;
}
}
The idea was to keep a cached version of the page for non-authenticated users, but avoid caching for authenticated ones.
I thought it will always return a Vary:Cookie
HTTP header, but it is not.
Doing a test with Fiddler and issuing twice the same request, in the first HTTP call it goes good:
HTTP/1.1 200 OK
Cache-Control: public, max-age=300
Content-Type: text/html; charset=utf-8
Expires: Thu, 09 Feb 2012 10:53:36 GMT
Last-Modified: Thu, 09 Feb 2012 10:48:36 GMT
Vary: Cookie
Server: Microsoft-IIS/7.5
X-AspNetMvc-Version: 3.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Thu, 09 Feb 2012 10:48:37 GMT
Content-Length: 441
But in the second one, it overwrites the header:
HTTP/1.1 200 OK
Cache-Control: public, max-age=297
Content-Type: text/html; charset=utf-8
Expires: Thu, 09 Feb 2012 10:53:36 GMT
Last-Modified: Thu, 09 Feb 2012 10:48:36 GMT
Vary: *
Server: Microsoft-IIS/7.5
X-AspNetMvc-Version: 3.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Thu, 09 Feb 2012 10:48:39 GMT
Content-Length: 441
So, as far as I know, browsers won't cache the request even if it is public, since Vary:*
means that the request has been generated with parameters that are not in the URL nor in the HTTP headers. Is there a way to fix this?
Regards.
UPDATE:
In a similar way, when I send two identical authenticated requests, the first call gets the private
modifier, but not the Vary
header:
HTTP/1.1 200 OK
Cache-Control: private, max-age=300
Content-Type: text/html; charset=utf-8
Expires: Thu, 09 Feb 2012 12:43:14 GMT
Last-Modified: Thu, 09 Feb 2012 12:38:14 GMT
Server: Microsoft-IIS/7.5
X-AspNetMvc-Version: 3.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Thu, 09 Feb 2012 12:38:14 GMT
Content-Length: 443
But the second one gets the same response that a non-authenticated request:
HTTP/1.1 200 OK
Cache-Control: public, max-age=298
Content-Type: text/html; charset=utf-8
Expires: Thu, 09 Feb 2012 12:44:32 GMT
Last-Modified: Thu, 09 Feb 2012 12:39:32 GMT
Vary: *
Server: Microsoft-IIS/7.5
X-AspNetMvc-Version: 3.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Thu, 09 Feb 2012 12:39:33 GMT
Content-Length: 443
I have uploaded a test project showing the issue so may be you want to give it a try.
Please be aware that there is an IHttpModule
that sets a request as authenticated or not depending on if the request has a cookie or not, this is not a "real life" approach, it is just for testing purposes.
The project contains only a web page with a link to itself, a link that logs you in, and another link that logs you out:
- LogIn : Sends a cookie in a
HTTP 302
redirection to the home page again. - LogOut: Sends a expired cookie in a
HTTP 302
recirection to the home page again.
The expected/ideal behaviour would be:
- User access Index, and get the page from the server. The page show date "A".
- User access Index again, and the browser shows the cached version.The page show date "A".
- Clean browser cache.
- User access Index again, and the browser shows the server cached version. The page show date "A".
- User clicks login, and the broswer gets a new page, that show date "B".
- User clicks logout, and the browser gets the server cached page. The page show date "A" again.
But this is the behaviour so far:
- User access Index, and get the page from the server. The page show date "A".
- User access Index again, and the browser shows the cached version.The page show date "A".
- Clean browser cache.
- User access Index again, and the browser shows the server cached version. The page show date "A".
- User clicks login, and the broswer gets a new page, that show date "B".
- User clicks logout, and the browser should get the server cached page, but it does not. The page show date "B" again from the browser cache. This is because the lack of the
Vary
header in the authenticated response.
I don't know if I get something wrong about caching, just missing some detail or the OutputCache
does not work very well, but I would appreciate any guidance.
Cheers.
UPDATE 2:
My intention is to use the HTTP cache semantics to:
- Allow browsers and proxys to cache the "public" version of the page.
- Allow browsers to cache the "authenticated" version of the page for its user.
If I change the OutputCache declaration to do the caching only on the server and prevent the downstream and client caching:
[OutputCache(Duration=60*5, Location=OutputCacheLocation.Server, VaryByCustom="index")]
it behaves as expected, but the downstream and client cache is prevented, and that is not what I want.
GetVaryByCustom
method? (Technically, you don't need to vary-by-cookie to achieve caching only for anonymous users.) – Premise