58

What's the best way to set cache control headers for public caching servers in WebAPI?

I'm not interested in OutputCache control on my server, I'm looking to control caching at the CDN side and beyond (I have individual API calls where the response can be indefinitely cached for the given URL) but everything I've read thus far either references pre-release versions of WebAPI (and thus references things that seem to no longer exist, like System.Web.HttpContext.Current.Reponse.Headers.CacheControl) or seems massively complicated for just setting a couple of http headers.

Is there a simple way to do this?

abatishchev
  • 98,240
  • 88
  • 296
  • 433

4 Answers4

103

As suggested in the comments, you can create an ActionFilterAttribute. Here's a simple one that only handles the MaxAge property:

public class CacheControlAttribute : System.Web.Http.Filters.ActionFilterAttribute
{
    public int MaxAge { get; set; }

    public CacheControlAttribute()
    {
        MaxAge = 3600;
    }

    public override void OnActionExecuted(HttpActionExecutedContext context)
    {
        if (context.Response != null)
            context.Response.Headers.CacheControl = new CacheControlHeaderValue()
            {
                Public = true,
                MaxAge = TimeSpan.FromSeconds(MaxAge)
            };

        base.OnActionExecuted(context);
    }
}

Then you can apply it to your methods:

 [CacheControl(MaxAge = 60)]
 public string GetFoo(int id)
 {
    // ...
 }
Jacob
  • 7,741
  • 4
  • 30
  • 24
  • This chace is only for the client side ? For server cache I should Strathweb.CacheOutput.WebApi2 setting the attribute to any , rigth ? – Lucas Roselli Sep 04 '15 at 18:58
81

The cache control header can be set like this.

public HttpResponseMessage GetFoo(int id)
{
    var foo = _FooRepository.GetFoo(id);
    var response = Request.CreateResponse(HttpStatusCode.OK, foo);
    response.Headers.CacheControl = new CacheControlHeaderValue()
        {
            Public = true,
            MaxAge = new TimeSpan(1, 0, 0, 0)
        };
    return response;
}
Youssef Moussaoui
  • 12,187
  • 2
  • 41
  • 37
Darrel Miller
  • 139,164
  • 32
  • 194
  • 243
  • 7
    Thats one of the approaches I know about, but I was hoping for something that was less invasive and specifically something that did not require you to remove useful information from the method declaration (going from a specific return type to a very generic one is not a good change imho). Thanks anyhow. –  Apr 10 '13 at 09:02
  • 3
    @Moo you could easily create an ActionFilterAttribute that does these few lines. Personally, I only use HttpResponseMessage as my return type, so the above style doesn't both me. HTTP is a generic interface, so reflecting that in my API layer makes sense to me. – Darrel Miller Apr 10 '13 at 11:20
  • its not so much that HTTP is a generic interface, its that the code is not - whatever it goes out as over the wire, its still useful to have it in the method declaration rather than buried in the code :) I shall look into the ActionFilterAttribute approach, thanks! –  Apr 11 '13 at 12:58
  • 3
    @Moo And if you could tell what went over the wire from looking at the signature I might agree. However, you have no idea what is going over the wire. It could be JSON, it could be a XMLSerializer serialization of the object, it could be DataContractSerialization of the object. You have no idea, unless you know all of the installed formatters, what order they were setup, what the accept header looks like, what the limits of the formatters are, etc, etc. – Darrel Miller Apr 11 '13 at 13:18
  • 3
    What goes out over the wire is only half the party tho, I have to write and maintain this stuff, and hand it over to other developers. Hiding what actually is returned in the method block rather than in the declaration means the docs have to be 100% spot on all the time, and we know how well that goes don't we... –  Apr 11 '13 at 16:10
  • @DarrelMiller please do you think you could help me with this question http://stackoverflow.com/questions/24235617/asp-net-web-api-what-problems-could-arise-if-i-use-post-instead-of-put-and-del/24235652#24235652 – eddy Jun 16 '14 at 02:37
  • How do I go about it, if I have used WebAPI 2 Attribute routing? – Vishnoo Rath Dec 30 '14 at 08:19
  • 1
    @VishnooRath It works the same whether you use attribute routing or regular routing – Darrel Miller Dec 31 '14 at 13:59
  • For me it works (returns cache control header with max age) when developing in VS 2019 but once deployed to IIS (10) I always get cache-control private. Is there a windows feature required or iis setting to enable this so we can have different max age headers for different web api methods and cache response on CDN? – DavidJBerman Jan 08 '21 at 08:32
9

In case anyone lands here looking for an answer specifically to ASP.NET Core, you can now do what @Jacob suggested without writing your own filter. Core already includes this:

[ResponseCache(VaryByHeader = "User-Agent", Duration = 1800)]
public async Task<JsonResult> GetData()
{
}

https://learn.microsoft.com/en-us/aspnet/core/performance/caching/response

mattferderer
  • 894
  • 9
  • 17
3

Like this answer suggesting filters, consider the "extended" version -- http://www.strathweb.com/2012/05/output-caching-in-asp-net-web-api/

It used to be available as a NuGet package Strathweb.CacheOutput.WebApi2, but doesn't seem to be hosted anymore, and is instead on GitHub -- https://github.com/filipw/AspNetWebApi-OutputCache

Community
  • 1
  • 1
drzaus
  • 24,171
  • 16
  • 142
  • 201