Disable browser cache for all actions, but keep it for bundles
Asked Answered
S

2

3

In an MVC App that I'm working on, we had to block browser cache on all actions for security reasons (preventing user from going back in history after he has logged out). We achieved this using this solution.

However, we do want to allow browsers to cache css and js bundles. Unfortunately the solution mentioned above appears to block caching of all resources. On local machine it even includes static files like images, though on remote server IIS handles those files (rather than the App itself) so that's one less thing to worry about. Anyway, is there some way to tweak this solution to allow bundles to be cached by browser?

I know that I could use a filter like this one and add it to all the actions (or even better, all controllers) or add a new base controller, that has this filter by default, and set all my controllers to inherit from it, but are there any better alternatives (ones that don't involve changing myriad files in the project)?

P.S. Having written this question has made me think of a few solutions that I have to try. This happened to me before. I mean, finding the right answer while writing a question here, but I ended up not posting those questions.


The solution which appeared to me while writing this question is really simple. Just write a simple if condition inside of Application_BeginRequest to determine if the resource should be cacheable or not based on the request url... I haven't tested it yet, but it sounds like it might just do the job.

Snappy answered 15/5, 2014 at 14:53 Comment(0)
S
2

Here is the solution that I mentioned in the original question. It's very simple (and somewhat dirty), but it appears to work.

protected void Application_BeginRequest()
{
    // Here we check if the request was for bundle or not and depending on that
    // apply the cache block.
    if (!Request.Url.PathAndQuery.StartsWith("/bundles/"))
    {
        Response.Cache.SetAllowResponseInBrowserHistory(false);
        Response.Cache.SetCacheability(HttpCacheability.NoCache);
        Response.Cache.SetNoStore();
        Response.Cache.SetExpires(DateTime.Now);
        Response.Cache.SetValidUntilExpires(true);
    }
}

In my local envoriment I also added /Content/ folder to the condition, but it's redundant on the remote server, since IIS will handle those (unless you explicitly tell it not to).

Snappy answered 16/5, 2014 at 12:49 Comment(0)
D
4

We accomplished your original requirement using a global filter. In Global.asax.cs:

GlobalFilters.Filters.Add(new NoCacheAttribute());

NoCacheAttribute:

/// <summary>
/// An attribute that can be attached to an individual controller or used globally to prevent the browser from caching the response.
/// This has nothing to do with server side caching, it simply alters the response headers with instructions for the browser.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class NoCacheAttribute : ActionFilterAttribute
{
    public override void OnResultExecuting(ResultExecutingContext filterContext)
    {
        if (!filterContext.IsChildAction && !(filterContext.Result is FileResult))
        {
            filterContext.HttpContext.Response.Cache.SetExpires(DateTime.MinValue);
            filterContext.HttpContext.Response.Cache.SetValidUntilExpires(false);
            filterContext.HttpContext.Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
            filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache);
            filterContext.HttpContext.Response.Cache.SetNoStore();
            base.OnResultExecuting(filterContext);
        }
    }
}

This affects all of our controller actions, but leaves static content and everything else alone. The Bundling framework handles its own caching: it basically tells browsers to cache forever, but includes a cache-busting token in the URL, which is a hash that changes if any of the bundled files are modified. This mechanism is unaffected by this filter. (I don't know if that's because global filters are not applied, or because it produces a FileResult--I suspect the former.)

Delight answered 15/5, 2014 at 15:12 Comment(0)
S
2

Here is the solution that I mentioned in the original question. It's very simple (and somewhat dirty), but it appears to work.

protected void Application_BeginRequest()
{
    // Here we check if the request was for bundle or not and depending on that
    // apply the cache block.
    if (!Request.Url.PathAndQuery.StartsWith("/bundles/"))
    {
        Response.Cache.SetAllowResponseInBrowserHistory(false);
        Response.Cache.SetCacheability(HttpCacheability.NoCache);
        Response.Cache.SetNoStore();
        Response.Cache.SetExpires(DateTime.Now);
        Response.Cache.SetValidUntilExpires(true);
    }
}

In my local envoriment I also added /Content/ folder to the condition, but it's redundant on the remote server, since IIS will handle those (unless you explicitly tell it not to).

Snappy answered 16/5, 2014 at 12:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.