stian.net
stian.net

Reputation: 3963

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.

Upvotes: 30

Views: 24761

Answers (6)

Brandon
Brandon

Reputation: 745

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

Upvotes: 1

Jarrod Moura
Jarrod Moura

Reputation: 31

What seems to work for me is giving the bundle a version number in the bundle config, then reference the new version in your markup.

Upvotes: 3

Kambiz Shahim
Kambiz Shahim

Reputation: 2590

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

Upvotes: 12

Saikat Ghosh
Saikat Ghosh

Reputation: 1

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

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

the last parameter is custom.

Upvotes: 0

Adilson de Almeida Jr
Adilson de Almeida Jr

Reputation: 2755

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

Upvotes: 2

Adam
Adam

Reputation: 28858

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

Upvotes: 4

Related Questions