Skip to content

Support for "cache-control:no-cache" in request (Take 2) #207

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion sample/WebApi.OutputCache.V2.Demo/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ class Program
{
static void Main(string[] args)
{
var config = new HttpSelfHostConfiguration("http://localhost:999");
const string hostUrl = "http://localhost:999";
var config = new HttpSelfHostConfiguration(hostUrl);
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
Expand All @@ -22,6 +23,7 @@ static void Main(string[] args)

server.OpenAsync().Wait();

Console.WriteLine($"WebAPI Hosted and listening on: {hostUrl}");
Console.ReadKey();

server.CloseAsync().Wait();
Expand Down
39 changes: 25 additions & 14 deletions src/WebApi.OutputCache.V2/CacheOutputAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public int SharedTimeSpan
get // required for property visibility
{
if (!_sharedTimeSpan.HasValue)
throw new Exception("should not be called without value set");
throw new Exception("should not be called without value set");
return _sharedTimeSpan.Value;
}
set { _sharedTimeSpan = value; }
Expand All @@ -80,7 +80,7 @@ public int SharedTimeSpan
/// Class used to generate caching keys
/// </summary>
public Type CacheKeyGenerator { get; set; }

// cache repository
private IApiOutputCache _webApiCache;

Expand All @@ -106,7 +106,7 @@ protected virtual bool IsCachingAllowed(HttpActionContext actionContext, bool an
return false;
}

return actionContext.Request.Method == HttpMethod.Get;
return actionContext.Request.Method == HttpMethod.Get;
}

protected virtual void EnsureCacheTimeQuery()
Expand Down Expand Up @@ -156,12 +156,20 @@ protected virtual MediaTypeHeaderValue GetExpectedMediaType(HttpConfiguration co
return responseMediaType;
}

private bool IsNoCacheHeaderInRequest(HttpActionContext actionContext)
{
var cacheControl = actionContext.Request.Headers.CacheControl;
return cacheControl != null && cacheControl.NoCache;
}

public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext == null) throw new ArgumentNullException("actionContext");

if (!IsCachingAllowed(actionContext, AnonymousOnly)) return;

if (IsNoCacheHeaderInRequest(actionContext)) return;

var config = actionContext.Request.GetConfiguration();

EnsureCacheTimeQuery();
Expand Down Expand Up @@ -208,8 +216,11 @@ public override void OnActionExecuting(HttpActionContext actionContext)
}

public override async Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
{
if (actionExecutedContext.ActionContext.Response == null || !actionExecutedContext.ActionContext.Response.IsSuccessStatusCode) return;
{
if (actionExecutedContext.ActionContext.Response == null ||
!actionExecutedContext.ActionContext.Response.IsSuccessStatusCode ||
IsNoCacheHeaderInRequest(actionExecutedContext.ActionContext))
return;

if (!IsCachingAllowed(actionExecutedContext.ActionContext, AnonymousOnly)) return;

Expand Down Expand Up @@ -242,12 +253,12 @@ public override async Task OnActionExecutedAsync(HttpActionExecutedContext actio
_webApiCache.Add(baseKey, string.Empty, cacheTime.AbsoluteExpiration);
_webApiCache.Add(cachekey, content, cacheTime.AbsoluteExpiration, baseKey);


_webApiCache.Add(cachekey + Constants.ContentTypeKey,
contentType,
cacheTime.AbsoluteExpiration, baseKey);


_webApiCache.Add(cachekey + Constants.EtagKey,
etag,
cacheTime.AbsoluteExpiration, baseKey);
Expand All @@ -263,12 +274,12 @@ protected virtual void ApplyCacheHeaders(HttpResponseMessage response, CacheTime
if (cacheTime.ClientTimeSpan > TimeSpan.Zero || MustRevalidate || Private)
{
var cachecontrol = new CacheControlHeaderValue
{
MaxAge = cacheTime.ClientTimeSpan,
SharedMaxAge = cacheTime.SharedTimeSpan,
MustRevalidate = MustRevalidate,
Private = Private
};
{
MaxAge = cacheTime.ClientTimeSpan,
SharedMaxAge = cacheTime.SharedTimeSpan,
MustRevalidate = MustRevalidate,
Private = Private
};

response.Headers.CacheControl = cachecontrol;
}
Expand All @@ -293,4 +304,4 @@ private static void SetEtag(HttpResponseMessage message, string etag)
}
}
}
}
}
58 changes: 37 additions & 21 deletions test/WebApi.OutputCache.V2.Tests/ClientSideTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,21 +84,21 @@ public void maxage_mustrevalidate_headers_correct_with_clienttimeout_zero_with_m
}


[Test]
public void nocache_headers_correct()
{
var client = new HttpClient(_server);
var result = client.GetAsync(_url + "Get_nocache").Result;

Assert.IsTrue(result.Headers.CacheControl.NoCache,
"NoCache in result headers was expected to be true when CacheOutput.NoCache=true.");
Assert.IsTrue(result.Headers.Contains("Pragma"),
"result headers does not contain expected Pragma.");
Assert.IsTrue(result.Headers.GetValues("Pragma").Contains("no-cache"),
"expected no-cache Pragma was not found");
}

[Test]
[Test]
public void nocache_headers_correct()
{
var client = new HttpClient(_server);
var result = client.GetAsync(_url + "Get_nocache").Result;

Assert.IsTrue(result.Headers.CacheControl.NoCache,
"NoCache in result headers was expected to be true when CacheOutput.NoCache=true.");
Assert.IsTrue(result.Headers.Contains("Pragma"),
"result headers does not contain expected Pragma.");
Assert.IsTrue(result.Headers.GetValues("Pragma").Contains("no-cache"),
"expected no-cache Pragma was not found");
}

[Test]
public void maxage_mustrevalidate_true_headers_correct()
{
var client = new HttpClient(_server);
Expand All @@ -122,9 +122,9 @@ public void maxage_private_true_headers_correct()
public void maxage_mustrevalidate_headers_correct_with_cacheuntil()
{
var client = new HttpClient(_server);
var result = client.GetAsync(_url + "Get_until25012015_1700").Result;
var clientTimeSpanSeconds = new SpecificTime(2017, 01, 25, 17, 0, 0).Execute(DateTime.Now).ClientTimeSpan.TotalSeconds;
var resultCacheControlSeconds = ((TimeSpan) result.Headers.CacheControl.MaxAge).TotalSeconds;
var result = client.GetAsync(_url + "Get_until25012020_1700").Result;
var clientTimeSpanSeconds = new SpecificTime(2020, 01, 25, 17, 0, 0).Execute(DateTime.Now).ClientTimeSpan.TotalSeconds;
var resultCacheControlSeconds = ((TimeSpan)result.Headers.CacheControl.MaxAge).TotalSeconds;
Assert.IsTrue(Math.Round(clientTimeSpanSeconds - resultCacheControlSeconds) == 0);
Assert.IsFalse(result.Headers.CacheControl.MustRevalidate);
}
Expand All @@ -135,7 +135,7 @@ public void maxage_mustrevalidate_headers_correct_with_cacheuntil_today()
var client = new HttpClient(_server);
var result = client.GetAsync(_url + "Get_until2355_today").Result;

Assert.IsTrue(Math.Round(new ThisDay(23,55,59).Execute(DateTime.Now).ClientTimeSpan.TotalSeconds - ((TimeSpan)result.Headers.CacheControl.MaxAge).TotalSeconds) == 0);
Assert.IsTrue(Math.Round(new ThisDay(23, 55, 59).Execute(DateTime.Now).ClientTimeSpan.TotalSeconds - ((TimeSpan)result.Headers.CacheControl.MaxAge).TotalSeconds) == 0);
Assert.IsFalse(result.Headers.CacheControl.MustRevalidate);
}

Expand All @@ -145,7 +145,7 @@ public void maxage_mustrevalidate_headers_correct_with_cacheuntil_this_month()
var client = new HttpClient(_server);
var result = client.GetAsync(_url + "Get_until27_thismonth").Result;

Assert.IsTrue(Math.Round(new ThisMonth(27,0,0,0).Execute(DateTime.Now).ClientTimeSpan.TotalSeconds - ((TimeSpan)result.Headers.CacheControl.MaxAge).TotalSeconds) == 0);
Assert.IsTrue(Math.Round(new ThisMonth(27, 0, 0, 0).Execute(DateTime.Now).ClientTimeSpan.TotalSeconds - ((TimeSpan)result.Headers.CacheControl.MaxAge).TotalSeconds) == 0);
Assert.IsFalse(result.Headers.CacheControl.MustRevalidate);
}

Expand Down Expand Up @@ -183,7 +183,7 @@ public void shared_max_age_header_correct()
{
var client = new HttpClient(_server);
var result = client.GetAsync(_url + "Get_c100_s100_sm200").Result;
Assert.AreEqual(result.Headers.CacheControl.SharedMaxAge,TimeSpan.FromSeconds(200));
Assert.AreEqual(result.Headers.CacheControl.SharedMaxAge, TimeSpan.FromSeconds(200));
}

[Test]
Expand All @@ -194,6 +194,22 @@ public void shared_max_age_header_not_present()
Assert.AreEqual(result.Headers.CacheControl.SharedMaxAge, null);
}

[Test]
public void no_caching_headers_in_response_when_nocache_header_present_in_request_headers()
{
var client = new HttpClient(_server);
client.DefaultRequestHeaders.CacheControl = new System.Net.Http.Headers.CacheControlHeaderValue
{
NoCache = true
};
var result = client.GetAsync(_url + "Get_c100_s100").Result;

Assert.IsNull(result.Headers.CacheControl,
"Cache-Control should not not be present in response when NoCache is present in request headers.");
Assert.IsNull(result.Headers.ETag,
"ETag should not not be present in response when NoCache is present in request headers.");
}

[TestFixtureTearDown]
public void fixture_dispose()
{
Expand Down
16 changes: 15 additions & 1 deletion test/WebApi.OutputCache.V2.Tests/ServerSideTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,26 @@ public void set_cache_to_predefined_value()
_cache.Verify(s => s.Add(It.Is<string>(x => x == "webapi.outputcache.v2.tests.testcontrollers.samplecontroller-get_c100_s100:application/json; charset=utf-8"), It.IsAny<object>(), It.Is<DateTimeOffset>(x => x <= DateTime.Now.AddSeconds(100)), It.Is<string>(x => x == "webapi.outputcache.v2.tests.testcontrollers.samplecontroller-get_c100_s100")), Times.Once());
}

[Test]
public void nocache_in_request_refreshes_cache()
{
var client = new HttpClient(_server);
client.DefaultRequestHeaders.CacheControl =
new CacheControlHeaderValue { NoCache = true };
var result = client.GetAsync(_url + "Get_c100_s100").Result;
_cache.Verify(s => s.Contains(It.Is<string>(x => x == "webapi.outputcache.v2.tests.testcontrollers.samplecontroller-get_c100_s100:application/json; charset=utf-8")), Times.Exactly(2));
_cache.Verify(s => s.Remove(It.Is<string>(x => x == "webapi.outputcache.v2.tests.testcontrollers.samplecontroller-get_c100_s100:application/json; charset=utf-8")), Times.Exactly(1));
_cache.Verify(s => s.Add(It.Is<string>(x => x == "webapi.outputcache.v2.tests.testcontrollers.samplecontroller-get_c100_s100"), It.IsAny<object>(), It.Is<DateTimeOffset>(x => x <= DateTime.Now.AddSeconds(100)), null), Times.Once());
_cache.Verify(s => s.Add(It.Is<string>(x => x == "webapi.outputcache.v2.tests.testcontrollers.samplecontroller-get_c100_s100:application/json; charset=utf-8"), It.IsAny<object>(), It.Is<DateTimeOffset>(x => x <= DateTime.Now.AddSeconds(100)), It.Is<string>(x => x == "webapi.outputcache.v2.tests.testcontrollers.samplecontroller-get_c100_s100")), Times.Once());
}


[Test]
public void set_cache_to_predefined_value_c100_s0()
{
var client = new HttpClient(_server);
var result = client.GetAsync(_url + "Get_c100_s0").Result;

// NOTE: Should we expect the _cache to not be called at all if the ServerTimeSpan is 0?
_cache.Verify(s => s.Contains(It.Is<string>(x => x == "webapi.outputcache.v2.tests.testcontrollers.samplecontroller-get_c100_s0:application/json; charset=utf-8")), Times.Once());
// NOTE: Server timespan is 0, so there should not have been any Add at all.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using WebApi.OutputCache.V2.TimeAttributes;

namespace WebApi.OutputCache.V2.Tests.TestControllers
Expand All @@ -25,17 +27,17 @@ public string Get_c0_s100()
return "test";
}

[CacheOutput(NoCache=true)]
[CacheOutput(NoCache = true)]
public string Get_nocache()
{
return "test";
}

[CacheOutput(ClientTimeSpan = 0, ServerTimeSpan = 100, MustRevalidate = true)]
public string Get_c0_s100_mustR()
{
return "test";
}
[CacheOutput(ClientTimeSpan = 0, ServerTimeSpan = 100, MustRevalidate = true)]
public string Get_c0_s100_mustR()
{
return "test";
}

[CacheOutput(ClientTimeSpan = 50, MustRevalidate = true)]
public string Get_c50_mustR()
Expand Down Expand Up @@ -64,7 +66,7 @@ public string Get_s50_exclude_fakecallback(int? id = null, string callback = nul
[CacheOutput(ServerTimeSpan = 50, ExcludeQueryStringFromCacheKey = false)]
public string Get_s50_exclude_false(int id)
{
return "test"+id;
return "test" + id;
}

[CacheOutput(ServerTimeSpan = 50, ExcludeQueryStringFromCacheKey = true)]
Expand All @@ -73,13 +75,13 @@ public string Get_s50_exclude_true(int id)
return "test" + id;
}

[CacheOutputUntil(2017,01,25,17,00)]
public string Get_until25012015_1700()
[CacheOutputUntil(2020, 01, 25, 17, 00)]
public string Get_until25012020_1700()
{
return "test";
}

[CacheOutputUntilToday(23,55)]
[CacheOutputUntilToday(23, 55)]
public string Get_until2355_today()
{
return "value";
Expand All @@ -91,7 +93,7 @@ public string Get_until27_thismonth()
return "value";
}

[CacheOutputUntilThisYear(7,31)]
[CacheOutputUntilThisYear(7, 31)]
public string Get_until731_thisyear()
{
return "value";
Expand Down Expand Up @@ -125,7 +127,7 @@ public string Get_request_exception_noCache()
[CacheOutput(ClientTimeSpan = 50, ServerTimeSpan = 50)]
public string Get_request_httpResponseException_noCache()
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Conflict){ReasonPhrase = "Fault shouldn't cache"});
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Conflict) { ReasonPhrase = "Fault shouldn't cache" });
}

[CacheOutput(ClientTimeSpan = 50, ServerTimeSpan = 50)]
Expand Down Expand Up @@ -160,4 +162,4 @@ public string Get_c100_s100_sm200()
}

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@
<HintPath>..\..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="nunit.framework, Version=2.6.2.12296, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\packages\NUnit.2.6.2\lib\nunit.framework.dll</HintPath>
<Reference Include="nunit.framework, Version=3.6.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>..\..\packages\NUnit.3.6.0\lib\net45\nunit.framework.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
Expand Down Expand Up @@ -116,6 +116,9 @@
<Name>WebApi2.OutputCache</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\nuget.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Expand Down
10 changes: 9 additions & 1 deletion test/WebApi.OutputCache.V2.Tests/packages.config
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,13 @@
<package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net45" userInstalled="true" />
<package id="Moq" version="4.0.10827" targetFramework="net45" userInstalled="true" />
<package id="Newtonsoft.Json" version="6.0.4" targetFramework="net45" userInstalled="true" />
<package id="NUnit" version="2.6.2" targetFramework="net45" userInstalled="true" />
<package id="NUnit" version="3.6.0" targetFramework="net45" userInstalled="true" />
<package id="NUnit.Console" version="3.6.0" targetFramework="net45" />
<package id="NUnit.ConsoleRunner" version="3.6.0" targetFramework="net45" />
<package id="NUnit.Extension.NUnitProjectLoader" version="3.5.0" targetFramework="net45" />
<package id="NUnit.Extension.NUnitV2Driver" version="3.6.0" targetFramework="net45" />
<package id="NUnit.Extension.NUnitV2ResultWriter" version="3.5.0" targetFramework="net45" />
<package id="NUnit.Extension.TeamCityEventListener" version="1.0.2" targetFramework="net45" />
<package id="NUnit.Extension.VSProjectLoader" version="3.5.0" targetFramework="net45" />
<package id="NUnit3TestAdapter" version="3.6.0" targetFramework="net45" />
</packages>