diff --git a/src/Microsoft.AspNetCore.ResponseCaching/IResponseCache.cs b/src/Microsoft.AspNetCore.ResponseCaching/Interfaces/IResponseCache.cs
similarity index 100%
rename from src/Microsoft.AspNetCore.ResponseCaching/IResponseCache.cs
rename to src/Microsoft.AspNetCore.ResponseCaching/Interfaces/IResponseCache.cs
diff --git a/src/Microsoft.AspNetCore.ResponseCaching/Interfaces/IResponseCachingCacheKeySuffixProvider.cs b/src/Microsoft.AspNetCore.ResponseCaching/Interfaces/IResponseCachingCacheKeySuffixProvider.cs
new file mode 100644
index 0000000000..7514fa4254
--- /dev/null
+++ b/src/Microsoft.AspNetCore.ResponseCaching/Interfaces/IResponseCachingCacheKeySuffixProvider.cs
@@ -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
+ {
+ ///
+ /// Create a key segment that is appended to the default cache key.
+ ///
+ /// The .
+ /// The key segment that will be appended to the default cache key.
+ string CreateCustomKeySuffix(HttpContext httpContext);
+ }
+}
diff --git a/src/Microsoft.AspNetCore.ResponseCaching/Interfaces/IResponseCachingCacheabilityValidator.cs b/src/Microsoft.AspNetCore.ResponseCaching/Interfaces/IResponseCachingCacheabilityValidator.cs
new file mode 100644
index 0000000000..11b46560c8
--- /dev/null
+++ b/src/Microsoft.AspNetCore.ResponseCaching/Interfaces/IResponseCachingCacheabilityValidator.cs
@@ -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
+ {
+ ///
+ /// Override default behavior for determining cacheability of an HTTP request.
+ ///
+ /// The .
+ /// The .
+ OverrideResult RequestIsCacheableOverride(HttpContext httpContext);
+
+ ///
+ /// Override default behavior for determining cacheability of an HTTP response.
+ ///
+ /// The .
+ /// The .
+ OverrideResult ResponseIsCacheableOverride(HttpContext httpContext);
+ }
+}
diff --git a/src/Microsoft.AspNetCore.ResponseCaching/Internal/NoopCacheKeySuffixProvider.cs b/src/Microsoft.AspNetCore.ResponseCaching/Internal/NoopCacheKeySuffixProvider.cs
new file mode 100644
index 0000000000..43daeb5a5d
--- /dev/null
+++ b/src/Microsoft.AspNetCore.ResponseCaching/Internal/NoopCacheKeySuffixProvider.cs
@@ -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;
+ }
+}
diff --git a/src/Microsoft.AspNetCore.ResponseCaching/Internal/NoopCacheabilityValidator.cs b/src/Microsoft.AspNetCore.ResponseCaching/Internal/NoopCacheabilityValidator.cs
new file mode 100644
index 0000000000..309e900f4b
--- /dev/null
+++ b/src/Microsoft.AspNetCore.ResponseCaching/Internal/NoopCacheabilityValidator.cs
@@ -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;
+ }
+}
diff --git a/src/Microsoft.AspNetCore.ResponseCaching/OverrideResult.cs b/src/Microsoft.AspNetCore.ResponseCaching/OverrideResult.cs
new file mode 100644
index 0000000000..e2a168aaeb
--- /dev/null
+++ b/src/Microsoft.AspNetCore.ResponseCaching/OverrideResult.cs
@@ -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
+ {
+ ///
+ /// Use the default logic for determining cacheability.
+ ///
+ UseDefaultLogic,
+
+ ///
+ /// Ignore default logic and do not cache.
+ ///
+ DoNotCache,
+
+ ///
+ /// Ignore default logic and cache.
+ ///
+ Cache
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.ResponseCaching/ResponseCachingContext.cs b/src/Microsoft.AspNetCore.ResponseCaching/ResponseCachingContext.cs
index 54f6384d4b..394d8f9c20 100644
--- a/src/Microsoft.AspNetCore.ResponseCaching/ResponseCachingContext.cs
+++ b/src/Microsoft.AspNetCore.ResponseCaching/ResponseCachingContext.cs
@@ -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)
diff --git a/src/Microsoft.AspNetCore.ResponseCaching/ResponseCachingExtensions.cs b/src/Microsoft.AspNetCore.ResponseCaching/ResponseCachingExtensions.cs
index 60817a6511..76b81dbccb 100644
--- a/src/Microsoft.AspNetCore.ResponseCaching/ResponseCachingExtensions.cs
+++ b/src/Microsoft.AspNetCore.ResponseCaching/ResponseCachingExtensions.cs
@@ -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();
}
}
diff --git a/src/Microsoft.AspNetCore.ResponseCaching/ResponseCachingMiddleware.cs b/src/Microsoft.AspNetCore.ResponseCaching/ResponseCachingMiddleware.cs
index 5c43ff15fe..c22ef33fb5 100644
--- a/src/Microsoft.AspNetCore.ResponseCaching/ResponseCachingMiddleware.cs
+++ b/src/Microsoft.AspNetCore.ResponseCaching/ResponseCachingMiddleware.cs
@@ -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