19

I'm trying to set cache headers in ASP.NET MVC Web API, but the response from IIS suggests that the CacheControl values set are being ignored.

My original assumption was that I was using the EnableCorsAttribute in System.Web.Http.Cors, which is necessary in this use case. However, even without that attribute, the response Cache-Control header is still 'private'.

Is there something I am doing wrong here?

    // GET api/<version>/content
    // [EnableCors(origins: "*", headers: "*", methods: "*")]
    public HttpResponseMessage Get(HttpRequestMessage request)
    {
        int cacheMaxAgeSeconds;

        string cacheMaxAgeString = request.GetQueryString("cache-max-age") ?? request.GetQueryString("cache-max-age-seconds");

        string rawUri = request.RequestUri.ToString();

        try
        {
            cacheMaxAgeSeconds = cacheMaxAgeString == null ? Config.ApiCacheControlMaxSeconds : int.Parse(cacheMaxAgeString);
        }
        catch (Exception ex)
        {
            cacheMaxAgeSeconds = Config.ApiCacheControlMaxSeconds;

            //... 
        }

        try
        {
            //...

            var response = new HttpResponseMessage(HttpStatusCode.OK)
            {
                Content = new StringContent("...", Encoding.UTF8, "application/json")
            };

            response.Headers.CacheControl = new CacheControlHeaderValue
            {
                Public = true,
                MaxAge = TimeSpan.FromSeconds(cacheMaxAgeSeconds)
            };

            return response;
        }
        catch (Exception apiEx)
        {
            //...
        }
    }

Response

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: application/json; charset=utf-8
Date: Thu, 23 Jul 2015 10:53:17 GMT
Server: Microsoft-IIS/7.5
Set-Cookie: ASP.NET_SessionId=knjh4pncbrhad30kjykvwxyz; path=/; HttpOnly
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Content-Length: 2367
Connection: keep-alive
abatishchev
  • 98,240
  • 88
  • 296
  • 433
gbro3n
  • 6,729
  • 9
  • 59
  • 100
  • is there a reason you are trying to roll your own here over something like https://github.com/filipw/AspNetWebApi-OutputCache ? – BMac Jul 30 '15 at 10:37
  • Yes, I actually want the caller to be able to specify the cache period. The value is read from the query string and feeds into the cache control header values. – gbro3n Jul 30 '15 at 14:30
  • hmm, if anyone will know the answer to this one it will be https://twitter.com/filip_woj (creator of above nuget package) might be worth reaching out to him on twitter. – BMac Jul 30 '15 at 19:17
  • Good call. Before I do that I will have a look through that project and see if anything is done differently. Will update when I've had a chance to check. – gbro3n Jul 31 '15 at 06:19

3 Answers3

6

Code below sets "cache-control: public, max-age=15" correctly in vanilla WebApi application (System.Web.Http 4.0.0.0). So... it's probably not the WebApi itself that causes the issue.

You may have some magic in your project that changes cache settings (think of global action filters or something similar). Or maybe you are going through proxy which rewrites HTTP headers.

    public HttpResponseMessage Get()
    {
        var content = new JavaScriptSerializer().Serialize(new { foo = "bar" });

        var response = new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = new StringContent(content, Encoding.UTF8, "application/json")
        };

        response.Headers.CacheControl = new CacheControlHeaderValue
        {
            Public = true,
            MaxAge = TimeSpan.FromSeconds(15)
        };

        return response;
    }

// returns in the response: "Cache-Control: public, max-age=15"
pkmiec
  • 2,594
  • 18
  • 16
5

The answer, having picked this up some weeks later:

Cache-Control header appears to be set to 'private' when running debug builds. The issue goes away when I run with a release build.

gbro3n
  • 6,729
  • 9
  • 59
  • 100
1

To add another thing that can cause this:

You run through an Owin pipeline.

In that case, you need to set the headers in an Owin middleware defined as this:

class MiddleWare : OwinMiddleware
{
    public MiddleWare(OwinMiddleware next)
    : base(next)
    {
    }
    public override async Task Invoke(IOwinContext context)
    {
        context.Response.Headers["Cache-Control"] = "no-cache, no-store, must-revalidate";
        context.Response.Headers["Pragma"] = "no-cache";
        context.Response.Headers["Expires"] = "0";
        await Next.Invoke(context);
    }
}
John
  • 6,693
  • 3
  • 51
  • 90