Skip to content
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,4 @@ Generated_Code #added for RIA/Silverlight projects
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
/.vs
28 changes: 28 additions & 0 deletions src/WebApi.OutputCache.Core/Time/PreciseTime.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;

namespace WebApi.OutputCache.Core.Time
{
public class PreciseTime : IModelQuery<DateTime, CacheTime>
{
private readonly TimeSpan _server;
private readonly TimeSpan _client;
private readonly TimeSpan? _shared;

public PreciseTime(TimeSpan server, TimeSpan client, TimeSpan? shared = null)
{
_server = server;
_client = client;
_shared = shared;
}

public CacheTime Execute(DateTime model)
{
return new CacheTime
{
AbsoluteExpiration = model.Add(_server),
ClientTimeSpan = _client,
SharedTimeSpan = _shared
};
}
}
}
1 change: 1 addition & 0 deletions src/WebApi.OutputCache.Core/WebApi.OutputCache.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
<Compile Include="IModelQuery.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Time\CacheTime.cs" />
<Compile Include="Time\PreciseTime.cs" />
<Compile Include="Time\ShortTime.cs" />
<Compile Include="Time\SpecificTime.cs" />
<Compile Include="Time\ThisDay.cs" />
Expand Down
89 changes: 80 additions & 9 deletions src/WebApi.OutputCache.V2/CacheOutputAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -41,30 +40,79 @@ public class CacheOutputAttribute : ActionFilterAttribute
public bool ExcludeQueryStringFromCacheKey { get; set; }

/// <summary>
/// How long response should be cached on the server side (in seconds)
/// How long response should be cached on the server side in seconds. <c cref="ServerTimeSpanMillis">ServerTimeSpanMillis</c> has precedence over this property.
/// </summary>
public int ServerTimeSpan { get; set; }

/// <summary>
/// Corresponds to CacheControl MaxAge HTTP header (in seconds)
/// Corresponds to CacheControl MaxAge HTTP header in seconds. <c cref="ClientTimeSpanMillis">ClientTimeSpanMillis</c> has precedence over this property.
/// </summary>
public int ClientTimeSpan { get; set; }


private int? _sharedTimeSpan = null;

/// <summary>
/// Corresponds to CacheControl Shared MaxAge HTTP header (in seconds)
/// Corresponds to CacheControl Shared MaxAge HTTP header in seconds. <c cref="SharedTimeSpanMillis">SharedTimeSpanMillis</c> has precedence over this property.
/// </summary>
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; }
set => _sharedTimeSpan = value;
}

/// <summary>
/// Corresponds to CacheControl MaxAge HTTP header in milliseconds. This property has precedence over <c cref="ClientTimeSpan">ClientTimeSpan</c>. If not set <c cref="ClientTimeSpan">ClientTimeSpan</c> is used.
/// </summary>
public ulong ClientTimeSpanMillis
{
get // required for property visibility
{
if (!_clientTimeSpanMillis.HasValue)
{
throw new Exception("should not be called without value set");
}
return _clientTimeSpanMillis.Value;
}
set => _clientTimeSpanMillis = value;
}

/// <summary>
/// How long response should be cached on the server side in milliseconds. This property has precedence over <c cref="ServerTimeSpan">ServerTimeSpan</c>. If not set <c cref="ServerTimeSpan">ServerTimeSpan</c> is used.
/// </summary>
public ulong ServerTimeSpanMillis
{
get // required for property visibility
{
if (!_serverTimeSpanMillis.HasValue)
{
throw new Exception("should not be called without value set");
}
return _serverTimeSpanMillis.Value;
}
set => _serverTimeSpanMillis = value;
}

/// <summary>
/// Corresponds to CacheControl Shared MaxAge HTTP header (string in TimeSpan's en-US format) in milliseconds. This property has precedence over <c cref="SharedTimeSpan">SharedTimeSpan</c>. If not set <c cref="SharedTimeSpan">SharedTimeSpan</c> is used.
/// </summary>
public ulong SharedTimeSpanMillis
{
get // required for property visibility
{
if (!_sharedTimeSpanMillis.HasValue)
{
throw new Exception("should not be called without value set");
}
return _sharedTimeSpanMillis.Value;
}
set => _sharedTimeSpanMillis = value;
}

/// <summary>
Expand Down Expand Up @@ -102,6 +150,10 @@ protected virtual void EnsureCache(HttpConfiguration config, HttpRequestMessage

internal IModelQuery<DateTime, CacheTime> CacheTimeQuery;

private ulong? _clientTimeSpanMillis;
private ulong? _serverTimeSpanMillis;
private ulong? _sharedTimeSpanMillis;

protected virtual bool IsCachingAllowed(HttpActionContext actionContext, bool anonymousOnly)
{
if (anonymousOnly)
Expand All @@ -127,7 +179,21 @@ protected virtual void EnsureCacheTimeQuery()

protected void ResetCacheTimeQuery()
{
CacheTimeQuery = new ShortTime( ServerTimeSpan, ClientTimeSpan, _sharedTimeSpan);
var serverTimeout = CreateTimeSpan(_serverTimeSpanMillis, ServerTimeSpan);
var clientTimeout = CreateTimeSpan(_clientTimeSpanMillis, ClientTimeSpan);

TimeSpan? sharedTimeout = null;

if (_sharedTimeSpanMillis.HasValue)
{
sharedTimeout = TimeSpan.FromMilliseconds(_sharedTimeSpanMillis.Value);
}
else if(_sharedTimeSpan.HasValue)
{
sharedTimeout = TimeSpan.FromSeconds(_sharedTimeSpan.Value);
}

CacheTimeQuery = new PreciseTime(serverTimeout, clientTimeout, sharedTimeout);
}

protected virtual MediaTypeHeaderValue GetExpectedMediaType(HttpConfiguration config, HttpActionContext actionContext)
Expand Down Expand Up @@ -372,5 +438,10 @@ private static void SetEtag(HttpResponseMessage message, string etag)
message.Headers.ETag = eTag;
}
}

private static TimeSpan CreateTimeSpan(ulong? millis, int seconds)
{
return millis.HasValue ? TimeSpan.FromMilliseconds(millis.Value) : TimeSpan.FromSeconds(seconds);
}
}
}
10 changes: 10 additions & 0 deletions test/WebApi.OutputCache.V2.Tests/ClientSideTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@ public void maxage_mustrevalidate_false_headers_correct()
Assert.IsFalse(result.Headers.CacheControl.MustRevalidate);
}

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

Assert.AreEqual(TimeSpan.FromMilliseconds(500), result.Headers.CacheControl.MaxAge);
Assert.IsFalse(result.Headers.CacheControl.MustRevalidate);
}

[Test]
public void no_cachecontrol_when_clienttimeout_is_zero()
{
Expand Down
12 changes: 12 additions & 0 deletions test/WebApi.OutputCache.V2.Tests/ServerSideTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,18 @@ 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 set_cache_to_predefined_value_using_string_timeout()
{
var client = new HttpClient(_server);
var result = client.GetAsync(_url + "GetStringTimeout_c500ms_s500ms").Result;

_cache.Verify(s => s.Contains(It.Is<string>(x => x == "webapi.outputcache.v2.tests.testcontrollers.samplecontroller-getstringtimeout_c500ms_s500ms:application/json; charset=utf-8")), Times.Exactly(2));
_cache.Verify(s => s.Add(It.Is<string>(x => x == "webapi.outputcache.v2.tests.testcontrollers.samplecontroller-getstringtimeout_c500ms_s500ms"), It.IsAny<object>(), It.Is<DateTimeOffset>(x => x < DateTime.Now.AddSeconds(1)), null), Times.Once());
_cache.Verify(s => s.Add(It.Is<string>(x => x == "webapi.outputcache.v2.tests.testcontrollers.samplecontroller-getstringtimeout_c500ms_s500ms:application/json; charset=utf-8"), It.IsAny<object>(), It.Is<DateTimeOffset>(x => x < DateTime.Now.AddSeconds(1)), It.Is<string>(x => x == "webapi.outputcache.v2.tests.testcontrollers.samplecontroller-getstringtimeout_c500ms_s500ms")), Times.Once());
}

[Test]
public void set_cache_to_predefined_value_c100_s0()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ public string Get_c100_s100()
return "test";
}

[CacheOutput(ClientTimeSpanMillis = 500, ServerTimeSpanMillis = 500)]
public string GetStringTimeout_c500ms_s500ms()
{
return "test";
}

[CacheOutput(ClientTimeSpan = 100, ServerTimeSpan = 0)]
public string Get_c100_s0()
{
Expand Down