Add configurable options for ResponseCaching

Override RequestIsCacheable

Override ResponseIsCacheable

Append customized cache key
This commit is contained in:
John Luo 2016-08-22 18:40:21 -07:00
parent 4f61c65931
commit fb724a71de
11 changed files with 402 additions and 84 deletions

View File

@ -0,0 +1,17 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.ResponseCaching
{
public interface IResponseCachingCacheKeySuffixProvider
{
/// <summary>
/// Create a key segment that is appended to the default cache key.
/// </summary>
/// <param name="httpContext">The <see cref="HttpContext"/>.</param>
/// <returns>The key segment that will be appended to the default cache key.</returns>
string CreateCustomKeySuffix(HttpContext httpContext);
}
}

View File

@ -0,0 +1,24 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.ResponseCaching
{
public interface IResponseCachingCacheabilityValidator
{
/// <summary>
/// Override default behavior for determining cacheability of an HTTP request.
/// </summary>
/// <param name="httpContext">The <see cref="HttpContext"/>.</param>
/// <returns>The <see cref="OverrideResult"/>.</returns>
OverrideResult RequestIsCacheableOverride(HttpContext httpContext);
/// <summary>
/// Override default behavior for determining cacheability of an HTTP response.
/// </summary>
/// <param name="httpContext">The <see cref="HttpContext"/>.</param>
/// <returns>The <see cref="OverrideResult"/>.</returns>
OverrideResult ResponseIsCacheableOverride(HttpContext httpContext);
}
}

View File

@ -0,0 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.ResponseCaching.Internal
{
internal class NoopCacheKeySuffixProvider : IResponseCachingCacheKeySuffixProvider
{
public string CreateCustomKeySuffix(HttpContext httpContext) => null;
}
}

View File

@ -0,0 +1,14 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.ResponseCaching.Internal
{
internal class NoopCacheabilityValidator : IResponseCachingCacheabilityValidator
{
public OverrideResult RequestIsCacheableOverride(HttpContext httpContext) => OverrideResult.UseDefaultLogic;
public OverrideResult ResponseIsCacheableOverride(HttpContext httpContext) => OverrideResult.UseDefaultLogic;
}
}

View File

@ -0,0 +1,23 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.ResponseCaching
{
public enum OverrideResult
{
/// <summary>
/// Use the default logic for determining cacheability.
/// </summary>
UseDefaultLogic,
/// <summary>
/// Ignore default logic and do not cache.
/// </summary>
DoNotCache,
/// <summary>
/// Ignore default logic and cache.
/// </summary>
Cache
}
}

View File

@ -28,31 +28,29 @@ namespace Microsoft.AspNetCore.ResponseCaching
private CachedResponse _cachedResponse;
private TimeSpan _cachedResponseValidFor;
internal DateTimeOffset _responseTime;
public ResponseCachingContext(HttpContext httpContext, IResponseCache cache)
: this(httpContext, cache, new SystemClock())
public ResponseCachingContext(
HttpContext httpContext,
IResponseCache cache,
IResponseCachingCacheabilityValidator cacheabilityValidator,
IResponseCachingCacheKeySuffixProvider cacheKeySuffixProvider)
: this(httpContext, cache, new SystemClock(), cacheabilityValidator, cacheKeySuffixProvider)
{
}
// Internal for testing
internal ResponseCachingContext(HttpContext httpContext, IResponseCache cache, ISystemClock clock)
internal ResponseCachingContext(
HttpContext httpContext,
IResponseCache cache,
ISystemClock clock,
IResponseCachingCacheabilityValidator cacheabilityValidator,
IResponseCachingCacheKeySuffixProvider cacheKeySuffixProvider)
{
if (cache == null)
{
throw new ArgumentNullException(nameof(cache));
}
if (httpContext == null)
{
throw new ArgumentNullException(nameof(httpContext));
}
if (clock == null)
{
throw new ArgumentNullException(nameof(clock));
}
HttpContext = httpContext;
Cache = cache;
Clock = clock;
CacheabilityValidator = cacheabilityValidator;
CacheKeySuffixProvider = cacheKeySuffixProvider;
}
internal bool CacheResponse
@ -72,12 +70,16 @@ namespace Microsoft.AspNetCore.ResponseCaching
internal bool ResponseStarted { get; set; }
private ISystemClock Clock { get; }
private HttpContext HttpContext { get; }
private IResponseCache Cache { get; }
private ISystemClock Clock { get; }
private IResponseCachingCacheabilityValidator CacheabilityValidator { get; }
private IResponseCachingCacheKeySuffixProvider CacheKeySuffixProvider { get; }
private Stream OriginalResponseStream { get; set; }
private ResponseCacheStream ResponseCacheStream { get; set; }
@ -145,46 +147,56 @@ namespace Microsoft.AspNetCore.ResponseCaching
var builder = new StringBuilder()
.Append(request.Method.ToUpperInvariant())
.Append(";")
.Append(request.Path.Value.ToUpperInvariant())
.Append(CreateVaryByCacheKey(varyBy));
.Append(request.Path.Value.ToUpperInvariant());
return builder.ToString();
}
private string CreateVaryByCacheKey(CachedVaryBy varyBy)
{
// TODO: resolve key format and delimiters
if (varyBy == null || varyBy.Headers.Count == 0)
if (varyBy?.Headers.Count > 0)
{
return string.Empty;
}
var builder = new StringBuilder(";");
foreach (var header in varyBy.Headers)
{
// TODO: Normalization of order, case?
var value = HttpContext.Request.Headers[header].ToString();
// TODO: How to handle null/empty string?
if (string.IsNullOrEmpty(value))
// TODO: resolve key format and delimiters
foreach (var header in varyBy.Headers)
{
value = "null";
// TODO: Normalization of order, case?
var value = HttpContext.Request.Headers[header];
// TODO: How to handle null/empty string?
if (StringValues.IsNullOrEmpty(value))
{
value = "null";
}
builder.Append(";")
.Append(header)
.Append("=")
.Append(value);
}
builder.Append(header)
.Append("=")
.Append(value)
.Append(";");
}
// TODO: Parse querystring params
// Parse querystring params
// Append custom cache key segment
var customKey = CacheKeySuffixProvider.CreateCustomKeySuffix(HttpContext);
if (!string.IsNullOrEmpty(customKey))
{
builder.Append(";")
.Append(customKey);
}
return builder.ToString();
}
internal bool RequestIsCacheable()
{
// Use optional override if specified by user
switch(CacheabilityValidator.RequestIsCacheableOverride(HttpContext))
{
case OverrideResult.UseDefaultLogic:
break;
case OverrideResult.DoNotCache:
return false;
case OverrideResult.Cache:
return true;
default:
throw new NotSupportedException($"Unrecognized result from {nameof(CacheabilityValidator.RequestIsCacheableOverride)}.");
}
// Verify the method
// TODO: RFC lists POST as a cacheable method when explicit freshness information is provided, but this is not widely implemented. Will revisit.
var request = HttpContext.Request;
@ -236,6 +248,19 @@ namespace Microsoft.AspNetCore.ResponseCaching
internal bool ResponseIsCacheable()
{
// Use optional override if specified by user
switch (CacheabilityValidator.ResponseIsCacheableOverride(HttpContext))
{
case OverrideResult.UseDefaultLogic:
break;
case OverrideResult.DoNotCache:
return false;
case OverrideResult.Cache:
return true;
default:
throw new NotSupportedException($"Unrecognized result from {nameof(CacheabilityValidator.ResponseIsCacheableOverride)}.");
}
// Only cache pages explicitly marked with public
// TODO: Consider caching responses that are not marked as public but otherwise cacheable?
if (!ResponseCacheControl.Public)

View File

@ -1,7 +1,9 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.ResponseCaching;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Builder
{
@ -9,6 +11,11 @@ namespace Microsoft.AspNetCore.Builder
{
public static IApplicationBuilder UseResponseCaching(this IApplicationBuilder app)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
return app.UseMiddleware<ResponseCachingMiddleware>();
}
}

View File

@ -4,11 +4,9 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.ResponseCaching
{
// http://tools.ietf.org/html/rfc7234
public class ResponseCachingMiddleware
{
private static readonly Func<object, Task> OnStartingCallback = state =>
@ -19,26 +17,45 @@ namespace Microsoft.AspNetCore.ResponseCaching
private readonly RequestDelegate _next;
private readonly IResponseCache _cache;
IResponseCachingCacheabilityValidator _cacheabilityValidator;
IResponseCachingCacheKeySuffixProvider _cacheKeySuffixProvider;
public ResponseCachingMiddleware(RequestDelegate next, IResponseCache cache)
public ResponseCachingMiddleware(
RequestDelegate next,
IResponseCache cache,
IResponseCachingCacheabilityValidator cacheabilityValidator,
IResponseCachingCacheKeySuffixProvider cacheKeySuffixProvider)
{
if (cache == null)
{
throw new ArgumentNullException(nameof(cache));
}
if (next == null)
{
throw new ArgumentNullException(nameof(next));
}
if (cacheabilityValidator == null)
{
throw new ArgumentNullException(nameof(cacheabilityValidator));
}
if (cacheKeySuffixProvider == null)
{
throw new ArgumentNullException(nameof(cacheKeySuffixProvider));
}
_next = next;
_cache = cache;
_cacheabilityValidator = cacheabilityValidator;
_cacheKeySuffixProvider = cacheKeySuffixProvider;
}
public async Task Invoke(HttpContext context)
{
var cachingContext = new ResponseCachingContext(context, _cache);
var cachingContext = new ResponseCachingContext(
context,
_cache,
_cacheabilityValidator,
_cacheKeySuffixProvider);
// Should we attempt any caching logic?
if (cachingContext.RequestIsCacheable())

View File

@ -18,6 +18,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
services.AddMemoryCache();
services.AddResponseCachingServices();
services.TryAdd(ServiceDescriptor.Singleton<IResponseCache, MemoryResponseCache>());
return services;
@ -31,9 +32,18 @@ namespace Microsoft.Extensions.DependencyInjection
}
services.AddDistributedMemoryCache();
services.AddResponseCachingServices();
services.TryAdd(ServiceDescriptor.Singleton<IResponseCache, DistributedResponseCache>());
return services;
}
private static IServiceCollection AddResponseCachingServices(this IServiceCollection services)
{
services.TryAdd(ServiceDescriptor.Singleton<IResponseCachingCacheKeySuffixProvider, NoopCacheKeySuffixProvider>());
services.TryAdd(ServiceDescriptor.Singleton<IResponseCachingCacheabilityValidator, NoopCacheabilityValidator>());
return services;
}
}
}

View File

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
@ -23,7 +22,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
{
var httpContext = new DefaultHttpContext();
httpContext.Request.Method = method;
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
var context = CreateTestContext(httpContext);
Assert.True(context.RequestIsCacheable());
}
@ -41,7 +40,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
{
var httpContext = new DefaultHttpContext();
httpContext.Request.Method = method;
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
var context = CreateTestContext(httpContext);
Assert.False(context.RequestIsCacheable());
}
@ -52,7 +51,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
var httpContext = new DefaultHttpContext();
httpContext.Request.Method = "GET";
httpContext.Request.Headers[HeaderNames.Authorization] = "Basic plaintextUN:plaintextPW";
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
var context = CreateTestContext(httpContext);
Assert.False(context.RequestIsCacheable());
}
@ -66,7 +65,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
{
NoCache = true
};
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
var context = CreateTestContext(httpContext);
Assert.False(context.RequestIsCacheable());
}
@ -80,7 +79,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
{
NoStore = true
};
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
var context = CreateTestContext(httpContext);
Assert.True(context.RequestIsCacheable());
}
@ -91,7 +90,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
var httpContext = new DefaultHttpContext();
httpContext.Request.Method = "GET";
httpContext.Request.Headers[HeaderNames.Pragma] = "no-cache";
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
var context = CreateTestContext(httpContext);
Assert.False(context.RequestIsCacheable());
}
@ -103,11 +102,61 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
httpContext.Request.Method = "GET";
httpContext.Request.Headers[HeaderNames.Pragma] = "no-cache";
httpContext.Request.Headers[HeaderNames.CacheControl] = "max-age=10";
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
var context = CreateTestContext(httpContext);
Assert.True(context.RequestIsCacheable());
}
private class AllowUnrecognizedHTTPMethodRequests : IResponseCachingCacheabilityValidator
{
public OverrideResult RequestIsCacheableOverride(HttpContext httpContext) =>
httpContext.Request.Method == "UNRECOGNIZED" ? OverrideResult.Cache : OverrideResult.DoNotCache;
public OverrideResult ResponseIsCacheableOverride(HttpContext httpContext) => OverrideResult.UseDefaultLogic;
}
[Fact]
public void RequestIsCacheableOverride_OverridesDefaultBehavior_ToAllowed()
{
var httpContext = new DefaultHttpContext();
httpContext.Request.Method = "UNRECOGNIZED";
var responseCachingContext = CreateTestContext(httpContext, new AllowUnrecognizedHTTPMethodRequests());
Assert.True(responseCachingContext.RequestIsCacheable());
}
private class DisallowGetHTTPMethodRequests : IResponseCachingCacheabilityValidator
{
public OverrideResult RequestIsCacheableOverride(HttpContext httpContext) =>
httpContext.Request.Method == "GET" ? OverrideResult.DoNotCache : OverrideResult.Cache;
public OverrideResult ResponseIsCacheableOverride(HttpContext httpContext) => OverrideResult.UseDefaultLogic;
}
[Fact]
public void RequestIsCacheableOverride_OverridesDefaultBehavior_ToNotAllowed()
{
var httpContext = new DefaultHttpContext();
httpContext.Request.Method = "GET";
var responseCachingContext = CreateTestContext(httpContext, new DisallowGetHTTPMethodRequests());
Assert.False(responseCachingContext.RequestIsCacheable());
}
[Fact]
public void RequestIsCacheableOverride_IgnoreFallsBackToDefaultBehavior()
{
var httpContext = new DefaultHttpContext();
httpContext.Request.Method = "GET";
var responseCachingContext = CreateTestContext(httpContext, new NoopCacheabilityValidator());
Assert.True(responseCachingContext.RequestIsCacheable());
httpContext.Request.Method = "UNRECOGNIZED";
Assert.False(responseCachingContext.RequestIsCacheable());
}
[Fact]
public void CreateCacheKey_Includes_UppercaseMethodAndPath()
{
@ -118,7 +167,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
httpContext.Request.Host = new HostString("example.com", 80);
httpContext.Request.PathBase = "/pathBase";
httpContext.Request.QueryString = new QueryString("?query.Key=a&query.Value=b");
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
var context = CreateTestContext(httpContext);
Assert.Equal("HEAD;/PATH/SUBPATH", context.CreateCacheKey());
}
@ -131,9 +180,30 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
httpContext.Request.Path = "/";
httpContext.Request.Headers["HeaderA"] = "ValueA";
httpContext.Request.Headers["HeaderB"] = "ValueB";
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
var context = CreateTestContext(httpContext);
Assert.Equal("GET;/;HeaderA=ValueA;HeaderC=null;", context.CreateCacheKey(new CachedVaryBy()
Assert.Equal("GET;/;HeaderA=ValueA;HeaderC=null", context.CreateCacheKey(new CachedVaryBy()
{
Headers = new string[] { "HeaderA", "HeaderC" }
}));
}
private class CustomizeKeySuffixProvider : IResponseCachingCacheKeySuffixProvider
{
public string CreateCustomKeySuffix(HttpContext httpContext) => "CustomizedKey";
}
[Fact]
public void CreateCacheKey_OptionalCacheKey_AppendedToDefaultKey()
{
var httpContext = new DefaultHttpContext();
httpContext.Request.Method = "GET";
httpContext.Request.Path = "/";
httpContext.Request.Headers["HeaderA"] = "ValueA";
httpContext.Request.Headers["HeaderB"] = "ValueB";
var responseCachingContext = CreateTestContext(httpContext, new CustomizeKeySuffixProvider());
Assert.Equal("GET;/;HeaderA=ValueA;HeaderC=null;CustomizedKey", responseCachingContext.CreateCacheKey(new CachedVaryBy()
{
Headers = new string[] { "HeaderA", "HeaderC" }
}));
@ -143,7 +213,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
public void ResponseIsCacheable_NoPublic_NotAllowed()
{
var httpContext = new DefaultHttpContext();
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
var context = CreateTestContext(httpContext);
Assert.False(context.ResponseIsCacheable());
}
@ -156,7 +226,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
{
Public = true
};
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
var context = CreateTestContext(httpContext);
Assert.True(context.ResponseIsCacheable());
}
@ -170,7 +240,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
Public = true,
NoCache = true
};
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
var context = CreateTestContext(httpContext);
Assert.False(context.ResponseIsCacheable());
}
@ -187,7 +257,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
{
Public = true
};
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
var context = CreateTestContext(httpContext);
Assert.False(context.ResponseIsCacheable());
}
@ -201,7 +271,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
Public = true,
NoStore = true
};
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
var context = CreateTestContext(httpContext);
Assert.False(context.ResponseIsCacheable());
}
@ -215,7 +285,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
Public = true
};
httpContext.Response.Headers[HeaderNames.Vary] = "*";
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
var context = CreateTestContext(httpContext);
Assert.False(context.ResponseIsCacheable());
}
@ -229,7 +299,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
Public = true,
Private = true
};
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
var context = CreateTestContext(httpContext);
Assert.False(context.ResponseIsCacheable());
}
@ -244,7 +314,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
{
Public = true
};
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
var context = CreateTestContext(httpContext);
Assert.True(context.ResponseIsCacheable());
}
@ -306,16 +376,78 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
{
Public = true
};
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
var context = CreateTestContext(httpContext);
Assert.False(context.ResponseIsCacheable());
}
private class Allow500Response : IResponseCachingCacheabilityValidator
{
public OverrideResult RequestIsCacheableOverride(HttpContext httpContext) => OverrideResult.UseDefaultLogic;
public OverrideResult ResponseIsCacheableOverride(HttpContext httpContext) =>
httpContext.Response.StatusCode == StatusCodes.Status500InternalServerError ? OverrideResult.Cache : OverrideResult.DoNotCache;
}
[Fact]
public void ResponseIsCacheableOverride_OverridesDefaultBehavior_ToAllowed()
{
var httpContext = new DefaultHttpContext();
httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
{
Public = true
};
var responseCachingContext = CreateTestContext(httpContext, new Allow500Response());
Assert.True(responseCachingContext.ResponseIsCacheable());
}
private class Disallow200Response : IResponseCachingCacheabilityValidator
{
public OverrideResult RequestIsCacheableOverride(HttpContext httpContext) => OverrideResult.UseDefaultLogic;
public OverrideResult ResponseIsCacheableOverride(HttpContext httpContext) =>
httpContext.Response.StatusCode == StatusCodes.Status200OK ? OverrideResult.DoNotCache : OverrideResult.Cache;
}
[Fact]
public void ResponseIsCacheableOverride_OverridesDefaultBehavior_ToNotAllowed()
{
var httpContext = new DefaultHttpContext();
httpContext.Response.StatusCode = StatusCodes.Status200OK;
httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
{
Public = true
};
var responseCachingContext = CreateTestContext(httpContext, new Disallow200Response());
Assert.False(responseCachingContext.ResponseIsCacheable());
}
[Fact]
public void ResponseIsCacheableOverride_IgnoreFallsBackToDefaultBehavior()
{
var httpContext = new DefaultHttpContext();
httpContext.Response.StatusCode = StatusCodes.Status200OK;
httpContext.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
{
Public = true
};
var responseCachingContext = CreateTestContext(httpContext, new NoopCacheabilityValidator());
Assert.True(responseCachingContext.ResponseIsCacheable());
httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
Assert.False(responseCachingContext.ResponseIsCacheable());
}
[Fact]
public void EntryIsFresh_NoExpiryRequirements_IsFresh()
{
var httpContext = new DefaultHttpContext();
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
var context = CreateTestContext(httpContext);
Assert.True(context.EntryIsFresh(new ResponseHeaders(new HeaderDictionary()), TimeSpan.MaxValue, verifyAgainstRequest: false));
}
@ -326,7 +458,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
var httpContext = new DefaultHttpContext();
var utcNow = DateTimeOffset.UtcNow;
httpContext.Response.GetTypedHeaders().Expires = utcNow;
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
var context = CreateTestContext(httpContext);
context._responseTime = utcNow;
Assert.False(context.EntryIsFresh(httpContext.Response.GetTypedHeaders(), TimeSpan.MaxValue, verifyAgainstRequest: false));
@ -345,7 +477,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
MaxAge = TimeSpan.FromSeconds(10)
};
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
var context = CreateTestContext(httpContext);
context._responseTime = utcNow;
Assert.True(context.EntryIsFresh(httpContext.Response.GetTypedHeaders(), TimeSpan.FromSeconds(10), verifyAgainstRequest: false));
@ -364,7 +496,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
MaxAge = TimeSpan.FromSeconds(10)
};
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
var context = CreateTestContext(httpContext);
context._responseTime = utcNow;
Assert.False(context.EntryIsFresh(httpContext.Response.GetTypedHeaders(), TimeSpan.FromSeconds(11), verifyAgainstRequest: false));
@ -379,7 +511,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
MaxAge = TimeSpan.FromSeconds(10),
SharedMaxAge = TimeSpan.FromSeconds(15)
};
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
var context = CreateTestContext(httpContext);
Assert.True(context.EntryIsFresh(httpContext.Response.GetTypedHeaders(), TimeSpan.FromSeconds(11), verifyAgainstRequest: false));
}
@ -393,7 +525,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
MaxAge = TimeSpan.FromSeconds(10),
SharedMaxAge = TimeSpan.FromSeconds(5)
};
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
var context = CreateTestContext(httpContext);
Assert.False(context.EntryIsFresh(httpContext.Response.GetTypedHeaders(), TimeSpan.FromSeconds(6), verifyAgainstRequest: false));
}
@ -411,7 +543,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
MaxAge = TimeSpan.FromSeconds(10),
SharedMaxAge = TimeSpan.FromSeconds(5)
};
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
var context = CreateTestContext(httpContext);
Assert.False(context.EntryIsFresh(httpContext.Response.GetTypedHeaders(), TimeSpan.FromSeconds(3), verifyAgainstRequest: true));
}
@ -428,7 +560,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
{
MaxAge = TimeSpan.FromSeconds(10),
};
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
var context = CreateTestContext(httpContext);
Assert.False(context.EntryIsFresh(httpContext.Response.GetTypedHeaders(), TimeSpan.FromSeconds(6), verifyAgainstRequest: true));
}
@ -447,7 +579,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
{
MaxAge = TimeSpan.FromSeconds(5),
};
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
var context = CreateTestContext(httpContext);
Assert.True(context.EntryIsFresh(httpContext.Response.GetTypedHeaders(), TimeSpan.FromSeconds(6), verifyAgainstRequest: true));
}
@ -467,7 +599,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
MaxAge = TimeSpan.FromSeconds(5),
MustRevalidate = true
};
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
var context = CreateTestContext(httpContext);
Assert.False(context.EntryIsFresh(httpContext.Response.GetTypedHeaders(), TimeSpan.FromSeconds(6), verifyAgainstRequest: true));
}
@ -486,11 +618,48 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
MaxAge = TimeSpan.FromSeconds(10),
SharedMaxAge = TimeSpan.FromSeconds(5)
};
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
var context = CreateTestContext(httpContext);
Assert.True(context.EntryIsFresh(httpContext.Response.GetTypedHeaders(), TimeSpan.FromSeconds(3), verifyAgainstRequest: false));
}
private static ResponseCachingContext CreateTestContext(HttpContext httpContext)
{
return CreateTestContext(
httpContext,
new NoopCacheKeySuffixProvider(),
new NoopCacheabilityValidator());
}
private static ResponseCachingContext CreateTestContext(HttpContext httpContext, IResponseCachingCacheKeySuffixProvider cacheKeySuffixProvider)
{
return CreateTestContext(
httpContext,
cacheKeySuffixProvider,
new NoopCacheabilityValidator());
}
private static ResponseCachingContext CreateTestContext(HttpContext httpContext, IResponseCachingCacheabilityValidator cacheabilityValidator)
{
return CreateTestContext(
httpContext,
new NoopCacheKeySuffixProvider(),
cacheabilityValidator);
}
private static ResponseCachingContext CreateTestContext(
HttpContext httpContext,
IResponseCachingCacheKeySuffixProvider cacheKeySuffixProvider,
IResponseCachingCacheabilityValidator cacheabilityValidator)
{
return new ResponseCachingContext(
httpContext,
new TestResponseCache(),
new SystemClock(),
cacheabilityValidator,
cacheKeySuffixProvider);
}
private class TestResponseCache : IResponseCache
{
public object Get(string key)