Add configurable options for ResponseCaching
Override RequestIsCacheable Override ResponseIsCacheable Append customized cache key
This commit is contained in:
parent
4f61c65931
commit
fb724a71de
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue