2

I am just starting with web-api 2 and stumbled upon : how to set caching settings. I have custom caching-message-handler like below (simplified for SO post)

public class CachingMessageHandler : DelegatingHandler
{
    private void SetCachingPolicy(HttpResponseMessage response)
    {
        response.Headers.CacheControl = new CacheControlHeaderValue
        {
            MaxAge = TimeSpan.FromSeconds(100),
        };
    }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        return base
            .SendAsync(request, cancellationToken)
            .ContinueWith(task =>
            {
                var response = task.Result;
                if (request.Method == HttpMethod.Get) SetCachingPolicy(response);
                return response;
            }, cancellationToken);
    }
}

and I have various hello-world HttpGet api's

    [HttpGet]
    public HttpResponseMessage Get1()
    {
        HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK, "value");
        response.Content = new StringContent("Hello World 1", Encoding.Unicode);
        return response;
    }

    [HttpGet]
    public HttpResponseMessage Get2()
    {
        return Request.CreateResponse(HttpStatusCode.OK, "Hello World 2");
    }

    [HttpGet]
    public HttpResponseMessage Get2_1()
    {
        var response = Request.CreateResponse(HttpStatusCode.OK, "Hello World 2");
        response.Headers.CacheControl = null;
        return response;
    }

    [HttpGet]
    public OkNegotiatedContentResult<string> Get3()
    {
        return Ok("Hello World 3");
    }

    [HttpGet]
    public string Get4()
    {
        return "Hello World 4";
    }

but the caching-message-handler is applied only for Get1 api, for all other Get2/3/4 api's, it seems some default caching setting is used. Below is the response in postman

enter image description here

Can someone please explain this behavior!!! why is caching only applied for Content-Type : text/plain and not for application/json

harishr
  • 17,807
  • 9
  • 78
  • 125
  • you're expecting `no-cache`. Why do you need `max-age` to be more than 0? – Khanh TO Apr 04 '15 at 09:37
  • `If a request includes the no-cache directive, it SHOULD NOT include min-fresh, max-stale, or max-age.`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.4 – Khanh TO Apr 04 '15 at 09:43
  • @KhanhTO even If i keep only max-age, thats still not getting reflected in API response in POSTMAN. But the `custom : 123` is getting reflected, then why the cache settings are ignored :( – harishr Apr 04 '15 at 10:22
  • What if you try: `task.Result.Content.Headers.CacheControl` – Khanh TO Apr 04 '15 at 14:33
  • try replacing `new CacheControlHeaderValue` with `new CacheControlHeaderValue()` (missing `()`) – Khanh TO Apr 04 '15 at 14:39
  • one more thing, you should not use `GetAwaiter()`, it should be used by the compiler instead of application code: https://msdn.microsoft.com/en-us/library/system.threading.tasks.task.getawaiter(v=vs.110).aspx . Try `return task.ContinueWith(() => ` instead: http://stackoverflow.com/questions/17428500/apicontroller-executed-method. That could also the problem. – Khanh TO Apr 04 '15 at 14:49
  • you question is tagged with OWIN. Do you have any middleware running after web api that happens to override the `cache-control` header? – Khanh TO Apr 04 '15 at 14:58
  • @KhanhTO i changed the code as per your comments, but still getting the same response.. no change in Cache-Control header, as expected. Why I expected no change, because I tried it with ActionFilterAttibute `OnActionExecuted` (without async) and that gave me the same behavior. also `()` this is not needed if you are initializing the object, is there anything else I can try? – harishr Apr 04 '15 at 16:20
  • @KhanhTO I am using only the web-api part. so no other middleware should change it. but somehow i think the cache-control settings is getting changed, because the `custom : 123` is making it to client side – harishr Apr 04 '15 at 16:26
  • @KhanhTO check the edit, found something, which completely changed the question – harishr Apr 04 '15 at 17:47

3 Answers3

2

I use angular and WebAPI with owin to build app too, and I have a custom cache filter too, but I use System.Web.Http.Filters.ActionFilterAttribute instead of DelegatingHandler , and it works for all response types, here is my custom cache filter code:

public class NoCacheFilter : System.Web.Http.Filters.ActionFilterAttribute {

    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) {
        if (actionExecutedContext.Request.Method == System.Net.Http.HttpMethod.Get && actionExecutedContext.Response != null) {
            actionExecutedContext.Response.Headers.CacheControl = new CacheControlHeaderValue {
                NoCache = true, NoStore = true, MustRevalidate = true,
                //MaxAge = TimeSpan.FromSeconds(100)
            };
            actionExecutedContext.Response.Headers.Pragma.Add(new NameValueHeaderValue("no-cache"));
            if (actionExecutedContext.Response.Content != null) {
                actionExecutedContext.Response.Content.Headers.Expires = DateTimeOffset.UtcNow;
            }
        }
        base.OnActionExecuted(actionExecutedContext);
    }
}

I register the filter in the Configure method like this:

var config = new HttpConfiguration();
config.Filters.Add(new NoCacheFilter());
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
);
app.UseWebApi(config);

I think maybe you should try to use ActionFilterAttribute.

zhimin
  • 2,740
  • 12
  • 22
1

You need to add Access-Control-Expose-Headers header at the backend. CORS returns certain response headers, so you need to tell it to return any other custom headers.

Access-Control-Expose-Headers: your-custom-cache-header-1, your-custom-cache-header-2, any-other-headers

Update

Since postman returns the same results, so it is not angular-related issue. It all resides on the server logic. Try to debug the lifecycle of text/plain and application/json requests. That might give you an idea why certain headers are not being set for application/json requests.

Muhammad Reda
  • 26,379
  • 14
  • 93
  • 105
  • i have `access-control-expose-header : *` – harishr Mar 30 '15 at 09:15
  • It is `access-control-expose-headers` with "s". And I am not sure it would work with "*". Set it to return a custom headers instead of "*". – Muhammad Reda Mar 30 '15 at 09:16
  • Looks like i am not allowed to type stars in comments :). So, replace "STAR" with string header names. – Muhammad Reda Mar 30 '15 at 09:23
  • with/without `s`, its generated by webapi, so I dont really expect typo issue there. btw, why should names work and not `*` ? – harishr Mar 30 '15 at 09:30
  • 1
    For security reasons some headers do not work with `*` while `useCredentials` set to `true`. – Muhammad Reda Mar 30 '15 at 09:43
  • how can I set useCredentials to false, we do not use cookies anyways, so I can set that to false, I have created a custom policy but that is not working as expected... would be great if you can provide any sample code.. should i write a separate question for that? – harishr Mar 30 '15 at 11:26
  • It should fe `false` by default. To change the value use this `app.config( function($httpProvider) { $httpProvider.defaults.withCredentials = false; });` – Muhammad Reda Mar 30 '15 at 11:41
  • i thought it should be set on server side and not client, :( – harishr Mar 30 '15 at 11:55
  • wanted to try your solution but facing another problem, can you please [have a look here](http://stackoverflow.com/questions/29345935/custom-cors-policy-not-working) – harishr Mar 31 '15 at 04:41
  • 1
    it should not be the problem. We need CORS only for cross-domain requests. In the picture of the question, `Access-Control-Expose-Headers : *` should allow all headers. – Khanh TO Apr 04 '15 at 09:45
  • @entre; where do you set `application/json` or `text/plain` in code? Do you want to deliver some pages as `json` and some as `plain text`? – Muhammad Reda Apr 06 '15 at 09:10
  • no ways.. thats just an observation, I am not adding any custom formatter on server side. the Conect-Type is sent from angular-side. – harishr Apr 06 '15 at 09:56
0

Ideally you should disable caching from server side. But since you tagged angularjs & you want to disable caching globally - you can do it like below

app.config(['$httpProvider', function($httpProvider) {
    if (!$httpProvider.defaults.headers.get)  $httpProvider.defaults.headers.get = {};    

    $httpProvider.defaults.headers.get['Cache-Control'] = 'no-cache';
    $httpProvider.defaults.headers.get['Pragma'] = 'no-cache';
}]);
algos
  • 238
  • 2
  • 8