简体   繁体   中英

MVC bundle client caching

By default a MVC bundle is cached on client for 1 year. Is it possible to set it's client headers manually (for 1 specific bundle)?

What I need is to set custom expire headers for one of my bundles. I can't rely on the "v=hash" querystring because this bundle is for an external website, and they won't change the url pointing to my bundle each time I change it.

What I've tried is to create a custom Bundle class (inherit Bundle) and overridde the GenerateBundleResponse() method. This way I can control the server caching, but the only way of customizing client caching is to set BundleResponse.Cacheability (public, private, nocache etc). But I can't set headers manually. I have access to the BundleContext (and it's HttpContext), but when I set headers on that context, it will have effect for all other requests as well.

Unfortunately there is no way. You can find the reason in the internal implementation of bundling. in the BundleHandler class ProcessRequest calls the ProcessRequest , internal method of the Bundle class and it calls SetHeaders just before the HttpContext.Response.Write . Therefore the client cache is set to one year just before the response write.

Note: BundleHandler is a internal sealed class: internal sealed class BundleHandler : IHttpHandler

In the BundleHandler class:

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

In the Bundle class:

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

The default behavior of the ASP.NET MVC bundling feature is that if any of the files that compose a bundle change - the query string for that bundle will automatically change - assuming you are using the following in your view's code:

@Scripts.Render("bundle name")

So this means if you have a new version of a file that is in a bundle, the next time your page renders a view that uses that bundle, it will send a script tag that the client browser will not found in its cache (since the query string is different).

So it seems like this will solve your problem - depends on what you mean by:

and they won't change the url pointing to my bundle each time I change it

对我来说似乎有效的方法是在捆绑软件配置中为捆绑软件提供一个版本号,然后在您的标记中引用新版本。

While there is not a better way to setup bundles cacheability, you can create a HttpModule which identifies the requests to the bundle and set the content cacheability.

You have the same effect doing this on the 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);
        }
    }

Pass an extra query string parameter to the url and change it everey time you want the cache to refresh.

eg: https://www.google.co.in/?gfe_rd=cr&ei=EwJeVbHWLcX08wfgwoCoBA&gws_rd=ssl&custom=abc

the last parameter is custom.

This is a modification of Adilson's answer, but without having to create an HttpModule:

In the global.asax.cs of the MVC project:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM