当呼叫到达缓存时,OutputCache发送错误的Vary标头

时间:2012-02-09 12:10:41

标签: asp.net asp.net-mvc-3 http caching outputcache

我有一个我想要缓存的动作方法:

[OutputCache(Duration=60*5, Location=OutputCacheLocation.Any, VaryByCustom="index")]
public ActionResult Index()
{
    return View();
}

采用这种方法:

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;
    }
}

这个想法是keep a cached version of the page for non-authenticated users, but avoid caching for authenticated ones

我认为它总会返回Vary:Cookie HTTP标头,但事实并非如此。 使用Fiddler进行测试并发出两次相同的请求,在第一次HTTP调用中它很顺利:

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

但在第二个中,它会覆盖标题:

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

因此,据我所知,浏览器即使是公共的,也不会缓存请求,因为Vary:*表示请求是使用不在URL中的参数生成的,也不是在HTTP头中生成的。有办法解决这个问题吗?

问候。

更新

以类似的方式,当我发送两个相同的经过身份验证的请求时,第一个调用会获得private修饰符,但不会获得Vary标题:

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

但是第二个获得与未经过身份验证的请求相同的响应:

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

我上传了test project showing the issue,所以您可能想尝试一下。

请注意,有一个IHttpModule将请求设置为已验证或不取决于请求是否包含Cookie,这不是“现实生活”方法,仅用于测试目的

该项目仅包含一个带有自身链接的网页,一个登录您的链接以及另一个将您注销的链接:

  • 登录:再次将HTTP 302重定向中的cookie发送到主页。
  • LogOut:再次将HTTP 302重定向中过期的Cookie发送到主页。

期望/理想行为将是:

  1. 用户访问索引,并从服务器获取页面。页面显示日期“A”。
  2. 用户再次访问索引,浏览器显示缓存版本。页面显示日期“A”。
  3. 清理浏览器缓存。
  4. 用户再次访问索引,浏览器显示服务器缓存版本。页面显示日期“A”。
  5. 用户点击登录,broswer获得一个新页面,显示日期“B”。
  6. 用户点击退出,浏览器获取服务器缓存页面。页面再次显示日期“A”。
  7. 但到目前为止这是行为:

    1. 用户访问索引,并从服务器获取页面。页面显示日期“A”。
    2. 用户再次访问索引,浏览器显示缓存版本。页面显示日期“A”。
    3. 清理浏览器缓存。
    4. 用户再次访问索引,浏览器显示服务器缓存版本。页面显示日期“A”。
    5. 用户点击登录,broswer获得一个新页面,显示日期“B”。
    6. 用户点击退出,浏览器应获取服务器缓存页面,但不是。页面再次从浏览器缓存中显示日期“B”。这是因为在经过身份验证的响应中缺少Vary标头。
    7. 我不知道我是否对缓存有任何不妥之处,只是遗漏了一些细节或者OutputCache不能很好地工作,但我会很感激任何指导。

      干杯。

      更新2:

      我的目的是使用HTTP缓存语义:

      1. 允许浏览器和代理缓存页面的“公共”版本。
      2. 允许浏览器为其用户缓存页面的“经过身份验证的”版本。
      3. 如果我更改OutputCache声明只在服务器上执行缓存并阻止下游和客户端缓存:

        [OutputCache(Duration=60*5, Location=OutputCacheLocation.Server, VaryByCustom="index")]
        

        它的行为符合预期,但是防止了下游和客户端缓存,这不是我想要的。

3 个答案:

答案 0 :(得分:4)

我认为[OutputCache]属性不是你想要的,VaryByCustom方法基本上是说我想根据这些参数缓存不同的版本,它实际上没有选项对于不缓存,属性中的大部分代码都是围绕基于服务器的缓存构建的。

据说MSDN上有关自定义缓存的文档似乎表明你需要根据身份验证状态返回一个字符串:

public override string GetVaryByCustomString(HttpContext context, string custom)
{
    if(custom == "user") return "User:" + context.Request.User.Identity.Name;

    return base.GetVaryByCustomString(context, custom);
}

然后使用VaryByCustom中的用户文字:

[OutputCache(Duration=60*5, Location=OutputCacheLocation.Any, VaryByCustom="user")]
public ActionResult Index()
{
    return View();
}

所以基本上这会导致为匿名构建缓存(假设匿名身份是空字符串或其他东西)和服务器上的每个用户,并且我相信发送给客户端的Vary: *。显然不是你想要的。

如果您真的只想使用HTTP缓存来缓存未经身份验证的版本,我建议您不要使用OutputCacheAttribute并使用更多自定义内容。

您可以轻松地在自己的自定义属性中编写类似于GetVaryByCustomString实现的内容(这只是一些伪代码,需要更多代码):

public class HttpCacheUnauthenticatedAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        if(!filterContext.HttpContext.Request.IsAuthenticated) {
            //TODO: set unauthenticated caching values and vary here
        }
    }
}

然后用它标记你的行动方法:

[HttpCacheUnauthenticated]
public ActionResult Index()
{
    return View();
}

答案 1 :(得分:1)

与我自己类似的摔跤。您是否尝试过使用web.config进行设置omitVaryStar=true

https://msdn.microsoft.com/en-us/library/ms228124(v=vs.100).aspx

答案 2 :(得分:0)

我正在使用自定义缓存提供程序,在这种情况下,有一个简单的解决方案。 在BeginRequest上,根据用户身份验证状态,我们将上下文信息设置为不运行缓存:

HttpContext.Current.Items["NoCache"] = "1";

然后在我们的GetVaryBy方法中,如果设置了此信息,则返回null:

public override string GetVaryByCustomString(HttpContext context, string custom)
{
    if (HttpContext.Current.Items["NoCache"] != null)
        return null;

    // remaining code here
}

然后在缓存方法上,我们可以测试相同的。例如:

public override object Add(string key, object entry, DateTime utcExpiry)
{
    if (HttpContext.Current.Items["NoCache"] != null)
        return null;

    // remaining code here
}