MVC捆绑客户端缓存

29

默认情况下,MVC bundle被客户端缓存1年。是否有可能手动设置其客户端头(针对1个特定的bundle)?

我需要的是为我的一个bundle设置自定义到期headers。我不能依赖于“v= hash”查询字符串,因为这个bundle是为外部网站而设计的,他们不会在每次更改时更改指向我的bundle的url。

我尝试创建一个自定义的Bundle类(继承Bundle)并覆盖GenerateBundleResponse()方法。这样我就可以控制服务器缓存,但是自定义客户端缓存的唯一方法是设置BundleResponse.Cacheability(public,private,nocache等)。但是我无法手动设置标头。我可以访问BundleContext(及其HttpContext),但是当我在该上下文中设置标头时,它对所有其他请求都会产生影响。


听起来你需要使用VaryByXXX,但看起来这种灵活性还没有实现:https://dev59.com/CWcs5IYBdhLWcg3wLQ84 - Grimace of Despair
6个回答

12

很遗憾,没有办法。您可以在打包的内部实现中找到原因。在BundleHandler类中,ProcessRequest调用了Bundle类的内部方法ProcessRequest,并在HttpContext.Response.Write之前调用了SetHeaders。因此,在响应写入之前,客户端缓存被设置为一年。

注意:BundleHandler是一个内部密封类:internal sealed class BundleHandler:IHttpHandler

BundleHandler类中:

public void ProcessRequest(HttpContext context)
{
    if (context == null)
    {
        throw new ArgumentNullException("context");
    }
    context.Response.Clear();
    BundleContext context2 = new BundleContext(new HttpContextWrapper(context), BundleTable.Bundles, this.BundleVirtualPath);
    if (!Bundle.GetInstrumentationMode(context2.HttpContext) && !string.IsNullOrEmpty(context.Request.Headers["If-Modified-Since"]))
    {
        context.Response.StatusCode = 304;
    }
    else
    {
        this.RequestBundle.ProcessRequest(context2);
    }
}

Bundle 类中:

internal void ProcessRequest(BundleContext context)
{
    context.EnableInstrumentation = GetInstrumentationMode(context.HttpContext);
    BundleResponse bundleResponse = this.GetBundleResponse(context);
    SetHeaders(bundleResponse, context);
    context.HttpContext.Response.Write(bundleResponse.Content);
}

private static void SetHeaders(BundleResponse bundle, BundleContext context)
{
    if (bundle.ContentType != null)
    {
        context.HttpContext.Response.ContentType = bundle.ContentType;
    }
    if (!context.EnableInstrumentation)
    {
        HttpCachePolicyBase cache = context.HttpContext.Response.Cache;
        cache.SetCacheability(bundle.Cacheability);
        cache.SetOmitVaryStar(true);
        cache.SetExpires(DateTime.Now.AddYears(1));
        cache.SetValidUntilExpires(true);
        cache.SetLastModified(DateTime.Now);
        cache.VaryByHeaders["User-Agent"] = true;
    }
}

2
请参考stackoverflow.com/a/34508048/1545567以解决此问题并完全控制缓存持续时间。 - Christophe Blin

4
ASP.NET MVC捆绑功能的默认行为是:如果组成捆绑包的任何文件发生更改,则该捆绑包的查询字符串将自动更改 - 假设您在视图代码中使用以下内容:
@Scripts.Render("bundle name")

这意味着如果您有一个新版本的捆绑文件,下次渲染使用该捆绑的视图时,它将发送一个脚本标记,客户端浏览器将在其缓存中找不到(因为查询字符串不同)。
所以看起来这会解决您的问题 - 这取决于您的意思是什么:
“并且他们不会每次更改我的捆绑时更改指向我的捆绑的url”

谢谢您的回答,但正如我所解释的,我不能依赖于“v = hash”查询字符串,因为这个捆绑包是为外部网站设计的。 - stian.net
1
我认为我理解了 - 你应该编辑你的问题,让它更清晰 - 你想让外部网站的页面包含<script src="/yoursite/bundlename?v=xxxxxxxx"></script>。 - Adam
@stian.net:只需将 ticks 添加到 bundle 的查询字符串中,无论版本如何。这将使客户端缓存失效。 - Stefan Steiger

3

对我而言,有效的方法是在bundle配置中给bundle分配一个版本号,然后在标记中引用新版本。


3
为什么?如果这是可行的解决方案,那么将其作为可行解决方案发布有什么问题吗? - DevOhrion
@cdomination 只有聪明的用户才能在评论区发帖,因为至少需要50个积分,否则只能回答问题。 - sairfan

2

虽然没有更好的设置bundles缓存的方法,但您可以创建一个HttpModule,用于识别对bundle的请求并设置内容缓存。

在Global.asax上执行此操作会产生相同的效果:

    public override void Init()
    {
        this.EndRequest += MvcApplication_EndRequest;
        base.Init();
    }

    void MvcApplication_EndRequest(object sender, EventArgs e)
    {
        var request = this.Request;
        var response = this.Response;

        if (request.RawUrl.Contains("Content/"))
        {
            response.Cache.SetCacheability(HttpCacheability.NoCache);
        }
    }

1
这是对Adilson答案的修改,但不需要创建HttpModule:
在MVC项目的global.asax.cs中:
protected void Application_EndRequest(object sender, EventArgs e) {
    if (Request.RawUrl.Contains("/bundles/")) {
        // My bundles all have a /bundles/ prefix in the URL
        Response.Cache.SetExpires(DateTime.Now.AddHours(2));
    }
}

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接