Add option for VaryBy query string params
This commit is contained in:
parent
8c5a5f7394
commit
1d6c5af72c
|
|
@ -8,5 +8,6 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
|
||||||
internal class CachedVaryBy
|
internal class CachedVaryBy
|
||||||
{
|
{
|
||||||
internal StringValues Headers { get; set; }
|
internal StringValues Headers { get; set; }
|
||||||
|
internal StringValues Params { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,12 +96,26 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialization Format
|
// Serialization Format
|
||||||
// Headers (comma separated string)
|
// Headers count
|
||||||
|
// Headers if count > 0 (comma separated string)
|
||||||
|
// Params count
|
||||||
|
// Params if count > 0 (comma separated string)
|
||||||
private static CachedVaryBy ReadCachedVaryBy(BinaryReader reader)
|
private static CachedVaryBy ReadCachedVaryBy(BinaryReader reader)
|
||||||
{
|
{
|
||||||
var headers = reader.ReadString().Split(',');
|
var headerCount = reader.ReadInt32();
|
||||||
|
var headers = new string[headerCount];
|
||||||
|
for (var index = 0; index < headerCount; index++)
|
||||||
|
{
|
||||||
|
headers[index] = reader.ReadString();
|
||||||
|
}
|
||||||
|
var paramCount = reader.ReadInt32();
|
||||||
|
var param = new string[paramCount];
|
||||||
|
for (var index = 0; index < paramCount; index++)
|
||||||
|
{
|
||||||
|
param[index] = reader.ReadString();
|
||||||
|
}
|
||||||
|
|
||||||
return new CachedVaryBy { Headers = headers };
|
return new CachedVaryBy { Headers = headers, Params = param };
|
||||||
}
|
}
|
||||||
|
|
||||||
// See serialization format above
|
// See serialization format above
|
||||||
|
|
@ -154,7 +168,18 @@ namespace Microsoft.AspNetCore.ResponseCaching.Internal
|
||||||
private static void WriteCachedVaryBy(BinaryWriter writer, CachedVaryBy entry)
|
private static void WriteCachedVaryBy(BinaryWriter writer, CachedVaryBy entry)
|
||||||
{
|
{
|
||||||
writer.Write(nameof(CachedVaryBy));
|
writer.Write(nameof(CachedVaryBy));
|
||||||
writer.Write(entry.Headers);
|
|
||||||
|
writer.Write(entry.Headers.Count);
|
||||||
|
foreach (var header in entry.Headers)
|
||||||
|
{
|
||||||
|
writer.Write(header);
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Write(entry.Params.Count);
|
||||||
|
foreach (var param in entry.Params)
|
||||||
|
{
|
||||||
|
writer.Write(param);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
@ -19,6 +20,8 @@ namespace Microsoft.AspNetCore.ResponseCaching
|
||||||
internal class ResponseCachingContext
|
internal class ResponseCachingContext
|
||||||
{
|
{
|
||||||
private static readonly CacheControlHeaderValue EmptyCacheControl = new CacheControlHeaderValue();
|
private static readonly CacheControlHeaderValue EmptyCacheControl = new CacheControlHeaderValue();
|
||||||
|
// Use the record separator for delimiting components of the cache key to avoid possible collisions
|
||||||
|
private static readonly char KeyDelimiter = '\x1e';
|
||||||
|
|
||||||
private readonly HttpContext _httpContext;
|
private readonly HttpContext _httpContext;
|
||||||
private readonly IResponseCache _cache;
|
private readonly IResponseCache _cache;
|
||||||
|
|
@ -150,13 +153,19 @@ namespace Microsoft.AspNetCore.ResponseCaching
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// Default key
|
||||||
builder
|
builder
|
||||||
.Append(request.Method.ToUpperInvariant())
|
.Append(request.Method.ToUpperInvariant())
|
||||||
.Append(";")
|
.Append(KeyDelimiter)
|
||||||
.Append(request.Path.Value.ToUpperInvariant());
|
.Append(request.Path.Value.ToUpperInvariant());
|
||||||
|
|
||||||
|
// Vary by headers
|
||||||
if (varyBy?.Headers.Count > 0)
|
if (varyBy?.Headers.Count > 0)
|
||||||
{
|
{
|
||||||
|
// Append a group separator for the header segment of the cache key
|
||||||
|
builder.Append(KeyDelimiter)
|
||||||
|
.Append('H');
|
||||||
|
|
||||||
// TODO: resolve key format and delimiters
|
// TODO: resolve key format and delimiters
|
||||||
foreach (var header in varyBy.Headers)
|
foreach (var header in varyBy.Headers)
|
||||||
{
|
{
|
||||||
|
|
@ -169,19 +178,62 @@ namespace Microsoft.AspNetCore.ResponseCaching
|
||||||
value = "null";
|
value = "null";
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.Append(";")
|
builder.Append(KeyDelimiter)
|
||||||
.Append(header)
|
.Append(header)
|
||||||
.Append("=")
|
.Append("=")
|
||||||
.Append(value);
|
.Append(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: Parse querystring params
|
|
||||||
|
// Vary by query params
|
||||||
|
if (varyBy?.Params.Count > 0)
|
||||||
|
{
|
||||||
|
// Append a group separator for the query parameter segment of the cache key
|
||||||
|
builder.Append(KeyDelimiter)
|
||||||
|
.Append('Q');
|
||||||
|
|
||||||
|
if (varyBy.Params.Count == 1 && string.Equals(varyBy.Params[0], "*"))
|
||||||
|
{
|
||||||
|
// Vary by all available query params
|
||||||
|
foreach (var query in _httpContext.Request.Query.OrderBy(q => q.Key, StringComparer.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
builder.Append(KeyDelimiter)
|
||||||
|
.Append(query.Key.ToUpperInvariant())
|
||||||
|
.Append("=")
|
||||||
|
.Append(query.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: resolve key format and delimiters
|
||||||
|
foreach (var param in varyBy.Params)
|
||||||
|
{
|
||||||
|
// TODO: Normalization of order, case?
|
||||||
|
var value = _httpContext.Request.Query[param];
|
||||||
|
|
||||||
|
// TODO: How to handle null/empty string?
|
||||||
|
if (StringValues.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
value = "null";
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Append(KeyDelimiter)
|
||||||
|
.Append(param)
|
||||||
|
.Append("=")
|
||||||
|
.Append(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Append custom cache key segment
|
// Append custom cache key segment
|
||||||
var customKey = _cacheKeySuffixProvider.CreateCustomKeySuffix(_httpContext);
|
var customKey = _cacheKeySuffixProvider.CreateCustomKeySuffix(_httpContext);
|
||||||
if (!string.IsNullOrEmpty(customKey))
|
if (!string.IsNullOrEmpty(customKey))
|
||||||
{
|
{
|
||||||
builder.Append(";")
|
// Append a group separator for the custom segment of the cache key
|
||||||
|
builder.Append(KeyDelimiter)
|
||||||
|
.Append('C');
|
||||||
|
|
||||||
|
builder.Append(KeyDelimiter)
|
||||||
.Append(customKey);
|
.Append(customKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -451,6 +503,7 @@ namespace Microsoft.AspNetCore.ResponseCaching
|
||||||
// Create the cache entry now
|
// Create the cache entry now
|
||||||
var response = _httpContext.Response;
|
var response = _httpContext.Response;
|
||||||
var varyHeaderValue = response.Headers[HeaderNames.Vary];
|
var varyHeaderValue = response.Headers[HeaderNames.Vary];
|
||||||
|
var varyParamsValue = _httpContext.GetResponseCachingFeature().VaryByParams;
|
||||||
_cachedResponseValidFor = ResponseCacheControl.SharedMaxAge
|
_cachedResponseValidFor = ResponseCacheControl.SharedMaxAge
|
||||||
?? ResponseCacheControl.MaxAge
|
?? ResponseCacheControl.MaxAge
|
||||||
?? (ResponseHeaders.Expires - _responseTime)
|
?? (ResponseHeaders.Expires - _responseTime)
|
||||||
|
|
@ -458,13 +511,18 @@ namespace Microsoft.AspNetCore.ResponseCaching
|
||||||
?? TimeSpan.FromSeconds(10);
|
?? TimeSpan.FromSeconds(10);
|
||||||
|
|
||||||
// Check if any VaryBy rules exist
|
// Check if any VaryBy rules exist
|
||||||
if (!StringValues.IsNullOrEmpty(varyHeaderValue))
|
if (!StringValues.IsNullOrEmpty(varyHeaderValue) || !StringValues.IsNullOrEmpty(varyParamsValue))
|
||||||
{
|
{
|
||||||
|
if (varyParamsValue.Count > 1)
|
||||||
|
{
|
||||||
|
Array.Sort(varyParamsValue.ToArray(), StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
var cachedVaryBy = new CachedVaryBy
|
var cachedVaryBy = new CachedVaryBy
|
||||||
{
|
{
|
||||||
// Only vary by headers for now
|
|
||||||
// TODO: VaryBy Encoding
|
// TODO: VaryBy Encoding
|
||||||
Headers = varyHeaderValue
|
Headers = varyHeaderValue,
|
||||||
|
Params = varyParamsValue
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Overwrite?
|
// TODO: Overwrite?
|
||||||
|
|
@ -536,6 +594,9 @@ namespace Microsoft.AspNetCore.ResponseCaching
|
||||||
{
|
{
|
||||||
_httpContext.Features.Set<IHttpSendFileFeature>(new SendFileFeatureWrapper(OriginalSendFileFeature, ResponseCacheStream));
|
_httpContext.Features.Set<IHttpSendFileFeature>(new SendFileFeatureWrapper(OriginalSendFileFeature, ResponseCacheStream));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Move this temporary interface with endpoint to HttpAbstractions
|
||||||
|
_httpContext.AddResponseCachingFeature();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void UnshimResponseStream()
|
internal void UnshimResponseStream()
|
||||||
|
|
@ -545,6 +606,9 @@ namespace Microsoft.AspNetCore.ResponseCaching
|
||||||
|
|
||||||
// Unshim IHttpSendFileFeature
|
// Unshim IHttpSendFileFeature
|
||||||
_httpContext.Features.Set(OriginalSendFileFeature);
|
_httpContext.Features.Set(OriginalSendFileFeature);
|
||||||
|
|
||||||
|
// TODO: Move this temporary interface with endpoint to HttpAbstractions
|
||||||
|
_httpContext.RemoveResponseCachingFeature();
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum ResponseType
|
private enum ResponseType
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using Microsoft.AspNetCore.ResponseCaching;
|
using Microsoft.AspNetCore.ResponseCaching;
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Builder
|
namespace Microsoft.AspNetCore.Builder
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
// 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.Extensions.Primitives;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.ResponseCaching
|
||||||
|
{
|
||||||
|
// TODO: Temporary interface for endpoints to specify options for response caching
|
||||||
|
public class ResponseCachingFeature
|
||||||
|
{
|
||||||
|
public StringValues VaryByParams { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
// 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
|
||||||
|
{
|
||||||
|
// TODO: Temporary interface for endpoints to specify options for response caching
|
||||||
|
public static class ResponseCachingHttpContextExtensions
|
||||||
|
{
|
||||||
|
public static void AddResponseCachingFeature(this HttpContext httpContext)
|
||||||
|
{
|
||||||
|
httpContext.Features.Set(new ResponseCachingFeature());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RemoveResponseCachingFeature(this HttpContext httpContext)
|
||||||
|
{
|
||||||
|
httpContext.Features.Set<ResponseCachingFeature>(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ResponseCachingFeature GetResponseCachingFeature(this HttpContext httpContext)
|
||||||
|
{
|
||||||
|
return httpContext.Features.Get<ResponseCachingFeature>() ?? new ResponseCachingFeature();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,19 +13,19 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
||||||
public class DefaultResponseCacheEntrySerializerTests
|
public class DefaultResponseCacheEntrySerializerTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void SerializeNullObjectThrows()
|
public void Serialize_NullObject_Throws()
|
||||||
{
|
{
|
||||||
Assert.Throws<ArgumentNullException>(() => DefaultResponseCacheSerializer.Serialize(null));
|
Assert.Throws<ArgumentNullException>(() => DefaultResponseCacheSerializer.Serialize(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void SerializeUnknownObjectThrows()
|
public void Serialize_UnknownObject_Throws()
|
||||||
{
|
{
|
||||||
Assert.Throws<NotSupportedException>(() => DefaultResponseCacheSerializer.Serialize(new object()));
|
Assert.Throws<NotSupportedException>(() => DefaultResponseCacheSerializer.Serialize(new object()));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void RoundTripCachedResponsesSucceeds()
|
public void RoundTrip_CachedResponses_Succeeds()
|
||||||
{
|
{
|
||||||
var headers = new HeaderDictionary();
|
var headers = new HeaderDictionary();
|
||||||
headers["keyA"] = "valueA";
|
headers["keyA"] = "valueA";
|
||||||
|
|
@ -42,7 +42,15 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void RoundTripCachedVaryBySucceeds()
|
public void RoundTrip_Empty_CachedVaryBy_Succeeds()
|
||||||
|
{
|
||||||
|
var cachedVaryBy = new CachedVaryBy();
|
||||||
|
|
||||||
|
AssertCachedVarybyEqual(cachedVaryBy, (CachedVaryBy)DefaultResponseCacheSerializer.Deserialize(DefaultResponseCacheSerializer.Serialize(cachedVaryBy)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RoundTrip_HeadersOnly_CachedVaryBy_Succeeds()
|
||||||
{
|
{
|
||||||
var headers = new[] { "headerA", "headerB" };
|
var headers = new[] { "headerA", "headerB" };
|
||||||
var cachedVaryBy = new CachedVaryBy()
|
var cachedVaryBy = new CachedVaryBy()
|
||||||
|
|
@ -53,9 +61,34 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
||||||
AssertCachedVarybyEqual(cachedVaryBy, (CachedVaryBy)DefaultResponseCacheSerializer.Deserialize(DefaultResponseCacheSerializer.Serialize(cachedVaryBy)));
|
AssertCachedVarybyEqual(cachedVaryBy, (CachedVaryBy)DefaultResponseCacheSerializer.Deserialize(DefaultResponseCacheSerializer.Serialize(cachedVaryBy)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RoundTrip_ParamsOnly_CachedVaryBy_Succeeds()
|
||||||
|
{
|
||||||
|
var param = new[] { "paramA", "paramB" };
|
||||||
|
var cachedVaryBy = new CachedVaryBy()
|
||||||
|
{
|
||||||
|
Params = param
|
||||||
|
};
|
||||||
|
|
||||||
|
AssertCachedVarybyEqual(cachedVaryBy, (CachedVaryBy)DefaultResponseCacheSerializer.Deserialize(DefaultResponseCacheSerializer.Serialize(cachedVaryBy)));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void DeserializeInvalidEntriesReturnsNull()
|
public void RoundTrip_HeadersAndParams_CachedVaryBy_Succeeds()
|
||||||
|
{
|
||||||
|
var headers = new[] { "headerA", "headerB" };
|
||||||
|
var param = new[] { "paramA", "paramB" };
|
||||||
|
var cachedVaryBy = new CachedVaryBy()
|
||||||
|
{
|
||||||
|
Headers = headers,
|
||||||
|
Params = param
|
||||||
|
};
|
||||||
|
|
||||||
|
AssertCachedVarybyEqual(cachedVaryBy, (CachedVaryBy)DefaultResponseCacheSerializer.Deserialize(DefaultResponseCacheSerializer.Serialize(cachedVaryBy)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Deserialize_InvalidEntries_ReturnsNull()
|
||||||
{
|
{
|
||||||
var headers = new[] { "headerA", "headerB" };
|
var headers = new[] { "headerA", "headerB" };
|
||||||
var cachedVaryBy = new CachedVaryBy()
|
var cachedVaryBy = new CachedVaryBy()
|
||||||
|
|
@ -87,6 +120,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
||||||
Assert.NotNull(actual);
|
Assert.NotNull(actual);
|
||||||
Assert.NotNull(expected);
|
Assert.NotNull(expected);
|
||||||
Assert.Equal(expected.Headers, actual.Headers);
|
Assert.Equal(expected.Headers, actual.Headers);
|
||||||
|
Assert.Equal(expected.Params, actual.Params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
||||||
{
|
{
|
||||||
public class ResponseCachingContextTests
|
public class ResponseCachingContextTests
|
||||||
{
|
{
|
||||||
|
private static readonly char KeyDelimiter = '\x1e';
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("GET")]
|
[InlineData("GET")]
|
||||||
[InlineData("HEAD")]
|
[InlineData("HEAD")]
|
||||||
|
|
@ -171,7 +173,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
||||||
httpContext.Request.QueryString = new QueryString("?query.Key=a&query.Value=b");
|
httpContext.Request.QueryString = new QueryString("?query.Key=a&query.Value=b");
|
||||||
var context = CreateTestContext(httpContext);
|
var context = CreateTestContext(httpContext);
|
||||||
|
|
||||||
Assert.Equal("HEAD;/PATH/SUBPATH", context.CreateCacheKey());
|
Assert.Equal($"HEAD{KeyDelimiter}/PATH/SUBPATH", context.CreateCacheKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -184,12 +186,77 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
||||||
httpContext.Request.Headers["HeaderB"] = "ValueB";
|
httpContext.Request.Headers["HeaderB"] = "ValueB";
|
||||||
var context = CreateTestContext(httpContext);
|
var context = CreateTestContext(httpContext);
|
||||||
|
|
||||||
Assert.Equal("GET;/;HeaderA=ValueA;HeaderC=null", context.CreateCacheKey(new CachedVaryBy()
|
Assert.Equal($"GET{KeyDelimiter}/{KeyDelimiter}H{KeyDelimiter}HeaderA=ValueA{KeyDelimiter}HeaderC=null", context.CreateCacheKey(new CachedVaryBy()
|
||||||
{
|
{
|
||||||
Headers = new string[] { "HeaderA", "HeaderC" }
|
Headers = new string[] { "HeaderA", "HeaderC" }
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreateCacheKey_Includes_ListedVaryByParamsOnly()
|
||||||
|
{
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.Request.Method = "GET";
|
||||||
|
httpContext.Request.Path = "/";
|
||||||
|
httpContext.Request.QueryString = new QueryString("?ParamA=ValueA&ParamB=ValueB");
|
||||||
|
var context = CreateTestContext(httpContext);
|
||||||
|
|
||||||
|
Assert.Equal($"GET{KeyDelimiter}/{KeyDelimiter}Q{KeyDelimiter}ParamA=ValueA{KeyDelimiter}ParamC=null", context.CreateCacheKey(new CachedVaryBy()
|
||||||
|
{
|
||||||
|
Params = new string[] { "ParamA", "ParamC" }
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreateCacheKey_Includes_VaryByParams_ParamNameCaseInsensitive_UseVaryByCasing()
|
||||||
|
{
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.Request.Method = "GET";
|
||||||
|
httpContext.Request.Path = "/";
|
||||||
|
httpContext.Request.QueryString = new QueryString("?parama=ValueA¶mB=ValueB");
|
||||||
|
var context = CreateTestContext(httpContext);
|
||||||
|
|
||||||
|
Assert.Equal($"GET{KeyDelimiter}/{KeyDelimiter}Q{KeyDelimiter}ParamA=ValueA{KeyDelimiter}ParamC=null", context.CreateCacheKey(new CachedVaryBy()
|
||||||
|
{
|
||||||
|
Params = new string[] { "ParamA", "ParamC" }
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreateCacheKey_Includes_AllQueryParamsGivenAsterisk()
|
||||||
|
{
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.Request.Method = "GET";
|
||||||
|
httpContext.Request.Path = "/";
|
||||||
|
httpContext.Request.QueryString = new QueryString("?ParamA=ValueA&ParamB=ValueB");
|
||||||
|
var context = CreateTestContext(httpContext);
|
||||||
|
|
||||||
|
// To support case insensitivity, all param keys are converted to lower case.
|
||||||
|
// Explicit VaryBy uses the casing specified in the setting.
|
||||||
|
Assert.Equal($"GET{KeyDelimiter}/{KeyDelimiter}Q{KeyDelimiter}PARAMA=ValueA{KeyDelimiter}PARAMB=ValueB", context.CreateCacheKey(new CachedVaryBy()
|
||||||
|
{
|
||||||
|
Params = new string[] { "*" }
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreateCacheKey_Includes_ListedVaryByHeadersAndParams()
|
||||||
|
{
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
httpContext.Request.Method = "GET";
|
||||||
|
httpContext.Request.Path = "/";
|
||||||
|
httpContext.Request.Headers["HeaderA"] = "ValueA";
|
||||||
|
httpContext.Request.Headers["HeaderB"] = "ValueB";
|
||||||
|
httpContext.Request.QueryString = new QueryString("?ParamA=ValueA&ParamB=ValueB");
|
||||||
|
var context = CreateTestContext(httpContext);
|
||||||
|
|
||||||
|
Assert.Equal($"GET{KeyDelimiter}/{KeyDelimiter}H{KeyDelimiter}HeaderA=ValueA{KeyDelimiter}HeaderC=null{KeyDelimiter}Q{KeyDelimiter}ParamA=ValueA{KeyDelimiter}ParamC=null", context.CreateCacheKey(new CachedVaryBy()
|
||||||
|
{
|
||||||
|
Headers = new string[] { "HeaderA", "HeaderC" },
|
||||||
|
Params = new string[] { "ParamA", "ParamC" }
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
private class CustomizeKeySuffixProvider : IResponseCachingCacheKeySuffixProvider
|
private class CustomizeKeySuffixProvider : IResponseCachingCacheKeySuffixProvider
|
||||||
{
|
{
|
||||||
public string CreateCustomKeySuffix(HttpContext httpContext) => "CustomizedKey";
|
public string CreateCustomKeySuffix(HttpContext httpContext) => "CustomizedKey";
|
||||||
|
|
@ -205,7 +272,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
||||||
httpContext.Request.Headers["HeaderB"] = "ValueB";
|
httpContext.Request.Headers["HeaderB"] = "ValueB";
|
||||||
var responseCachingContext = CreateTestContext(httpContext, new CustomizeKeySuffixProvider());
|
var responseCachingContext = CreateTestContext(httpContext, new CustomizeKeySuffixProvider());
|
||||||
|
|
||||||
Assert.Equal("GET;/;HeaderA=ValueA;HeaderC=null;CustomizedKey", responseCachingContext.CreateCacheKey(new CachedVaryBy()
|
Assert.Equal($"GET{KeyDelimiter}/{KeyDelimiter}H{KeyDelimiter}HeaderA=ValueA{KeyDelimiter}HeaderC=null{KeyDelimiter}C{KeyDelimiter}CustomizedKey", responseCachingContext.CreateCacheKey(new CachedVaryBy()
|
||||||
{
|
{
|
||||||
Headers = new string[] { "HeaderA", "HeaderC" }
|
Headers = new string[] { "HeaderA", "HeaderC" }
|
||||||
}));
|
}));
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
|
@ -18,7 +19,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
||||||
public class ResponseCachingTests
|
public class ResponseCachingTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public async void ServesCachedContentIfAvailable()
|
public async void ServesCachedContent_IfAvailable()
|
||||||
{
|
{
|
||||||
var builder = CreateBuilderWithResponseCaching(async (context) =>
|
var builder = CreateBuilderWithResponseCaching(async (context) =>
|
||||||
{
|
{
|
||||||
|
|
@ -40,20 +41,12 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
||||||
var initialResponse = await client.GetAsync("");
|
var initialResponse = await client.GetAsync("");
|
||||||
var subsequentResponse = await client.GetAsync("");
|
var subsequentResponse = await client.GetAsync("");
|
||||||
|
|
||||||
initialResponse.EnsureSuccessStatusCode();
|
await AssertResponseCachedAsync(initialResponse, subsequentResponse);
|
||||||
subsequentResponse.EnsureSuccessStatusCode();
|
|
||||||
|
|
||||||
foreach (var header in initialResponse.Headers)
|
|
||||||
{
|
|
||||||
Assert.Equal(initialResponse.Headers.GetValues(header.Key), subsequentResponse.Headers.GetValues(header.Key));
|
|
||||||
}
|
|
||||||
Assert.True(subsequentResponse.Headers.Contains(HeaderNames.Age));
|
|
||||||
Assert.Equal(await initialResponse.Content.ReadAsStringAsync(), await subsequentResponse.Content.ReadAsStringAsync());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async void ServesFreshContentIfNotAvailable()
|
public async void ServesFreshContent_IfNotAvailable()
|
||||||
{
|
{
|
||||||
var builder = CreateBuilderWithResponseCaching(async (context) =>
|
var builder = CreateBuilderWithResponseCaching(async (context) =>
|
||||||
{
|
{
|
||||||
|
|
@ -75,16 +68,12 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
||||||
var initialResponse = await client.GetAsync("");
|
var initialResponse = await client.GetAsync("");
|
||||||
var subsequentResponse = await client.GetAsync("/different");
|
var subsequentResponse = await client.GetAsync("/different");
|
||||||
|
|
||||||
initialResponse.EnsureSuccessStatusCode();
|
await AssertResponseNotCachedAsync(initialResponse, subsequentResponse);
|
||||||
subsequentResponse.EnsureSuccessStatusCode();
|
|
||||||
|
|
||||||
Assert.False(subsequentResponse.Headers.Contains(HeaderNames.Age));
|
|
||||||
Assert.NotEqual(await initialResponse.Content.ReadAsStringAsync(), await subsequentResponse.Content.ReadAsStringAsync());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async void ServesCachedContentIfVaryByMatches()
|
public async void ServesCachedContent_IfVaryByHeader_Matches()
|
||||||
{
|
{
|
||||||
var builder = CreateBuilderWithResponseCaching(async (context) =>
|
var builder = CreateBuilderWithResponseCaching(async (context) =>
|
||||||
{
|
{
|
||||||
|
|
@ -108,20 +97,284 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
||||||
var initialResponse = await client.GetAsync("");
|
var initialResponse = await client.GetAsync("");
|
||||||
var subsequentResponse = await client.GetAsync("");
|
var subsequentResponse = await client.GetAsync("");
|
||||||
|
|
||||||
initialResponse.EnsureSuccessStatusCode();
|
await AssertResponseCachedAsync(initialResponse, subsequentResponse);
|
||||||
subsequentResponse.EnsureSuccessStatusCode();
|
|
||||||
|
|
||||||
foreach (var header in initialResponse.Headers)
|
|
||||||
{
|
|
||||||
Assert.Equal(initialResponse.Headers.GetValues(header.Key), subsequentResponse.Headers.GetValues(header.Key));
|
|
||||||
}
|
|
||||||
Assert.True(subsequentResponse.Headers.Contains(HeaderNames.Age));
|
|
||||||
Assert.Equal(await initialResponse.Content.ReadAsStringAsync(), await subsequentResponse.Content.ReadAsStringAsync());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async void ServesFreshContentIfRequestRequirementsNotMet()
|
public async void ServesFreshContent_IfVaryByHeader_Mismatches()
|
||||||
|
{
|
||||||
|
var builder = CreateBuilderWithResponseCaching(async (context) =>
|
||||||
|
{
|
||||||
|
var uniqueId = Guid.NewGuid().ToString();
|
||||||
|
var headers = context.Response.GetTypedHeaders();
|
||||||
|
headers.CacheControl = new CacheControlHeaderValue()
|
||||||
|
{
|
||||||
|
Public = true,
|
||||||
|
MaxAge = TimeSpan.FromSeconds(10)
|
||||||
|
};
|
||||||
|
headers.Date = DateTimeOffset.UtcNow;
|
||||||
|
headers.Headers["X-Value"] = uniqueId;
|
||||||
|
context.Response.Headers[HeaderNames.Vary] = HeaderNames.From;
|
||||||
|
await context.Response.WriteAsync(uniqueId);
|
||||||
|
});
|
||||||
|
|
||||||
|
using (var server = new TestServer(builder))
|
||||||
|
{
|
||||||
|
var client = server.CreateClient();
|
||||||
|
client.DefaultRequestHeaders.From = "user@example.com";
|
||||||
|
var initialResponse = await client.GetAsync("");
|
||||||
|
client.DefaultRequestHeaders.From = "user2@example.com";
|
||||||
|
var subsequentResponse = await client.GetAsync("");
|
||||||
|
|
||||||
|
await AssertResponseNotCachedAsync(initialResponse, subsequentResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async void ServesCachedContent_IfVaryByParams_Matches()
|
||||||
|
{
|
||||||
|
var builder = CreateBuilderWithResponseCaching(async (context) =>
|
||||||
|
{
|
||||||
|
var uniqueId = Guid.NewGuid().ToString();
|
||||||
|
var headers = context.Response.GetTypedHeaders();
|
||||||
|
headers.CacheControl = new CacheControlHeaderValue()
|
||||||
|
{
|
||||||
|
Public = true,
|
||||||
|
MaxAge = TimeSpan.FromSeconds(10)
|
||||||
|
};
|
||||||
|
headers.Date = DateTimeOffset.UtcNow;
|
||||||
|
headers.Headers["X-Value"] = uniqueId;
|
||||||
|
context.GetResponseCachingFeature().VaryByParams = "param";
|
||||||
|
await context.Response.WriteAsync(uniqueId);
|
||||||
|
});
|
||||||
|
|
||||||
|
using (var server = new TestServer(builder))
|
||||||
|
{
|
||||||
|
var client = server.CreateClient();
|
||||||
|
var initialResponse = await client.GetAsync("?param=value");
|
||||||
|
var subsequentResponse = await client.GetAsync("?param=value");
|
||||||
|
|
||||||
|
await AssertResponseCachedAsync(initialResponse, subsequentResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async void ServesCachedContent_IfVaryByParamsExplicit_Matches_ParamNameCaseInsensitive()
|
||||||
|
{
|
||||||
|
var builder = CreateBuilderWithResponseCaching(
|
||||||
|
app =>
|
||||||
|
{
|
||||||
|
app.Use(async (context, next) =>
|
||||||
|
{
|
||||||
|
context.Features.Set<IHttpSendFileFeature>(new DummySendFileFeature());
|
||||||
|
await next.Invoke();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async (context) =>
|
||||||
|
{
|
||||||
|
var uniqueId = Guid.NewGuid().ToString();
|
||||||
|
var headers = context.Response.GetTypedHeaders();
|
||||||
|
headers.CacheControl = new CacheControlHeaderValue()
|
||||||
|
{
|
||||||
|
Public = true,
|
||||||
|
MaxAge = TimeSpan.FromSeconds(10)
|
||||||
|
};
|
||||||
|
headers.Date = DateTimeOffset.UtcNow;
|
||||||
|
headers.Headers["X-Value"] = uniqueId;
|
||||||
|
context.GetResponseCachingFeature().VaryByParams = new[] { "ParamA", "paramb" };
|
||||||
|
await context.Response.WriteAsync(uniqueId);
|
||||||
|
});
|
||||||
|
|
||||||
|
using (var server = new TestServer(builder))
|
||||||
|
{
|
||||||
|
var client = server.CreateClient();
|
||||||
|
var initialResponse = await client.GetAsync("?parama=valuea¶mb=valueb");
|
||||||
|
var subsequentResponse = await client.GetAsync("?ParamA=valuea&ParamB=valueb");
|
||||||
|
|
||||||
|
await AssertResponseCachedAsync(initialResponse, subsequentResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async void ServesCachedContent_IfVaryByParamsStar_Matches_ParamNameCaseInsensitive()
|
||||||
|
{
|
||||||
|
var builder = CreateBuilderWithResponseCaching(
|
||||||
|
app =>
|
||||||
|
{
|
||||||
|
app.Use(async (context, next) =>
|
||||||
|
{
|
||||||
|
context.Features.Set<IHttpSendFileFeature>(new DummySendFileFeature());
|
||||||
|
await next.Invoke();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async (context) =>
|
||||||
|
{
|
||||||
|
var uniqueId = Guid.NewGuid().ToString();
|
||||||
|
var headers = context.Response.GetTypedHeaders();
|
||||||
|
headers.CacheControl = new CacheControlHeaderValue()
|
||||||
|
{
|
||||||
|
Public = true,
|
||||||
|
MaxAge = TimeSpan.FromSeconds(10)
|
||||||
|
};
|
||||||
|
headers.Date = DateTimeOffset.UtcNow;
|
||||||
|
headers.Headers["X-Value"] = uniqueId;
|
||||||
|
context.GetResponseCachingFeature().VaryByParams = new[] { "*" };
|
||||||
|
await context.Response.WriteAsync(uniqueId);
|
||||||
|
});
|
||||||
|
|
||||||
|
using (var server = new TestServer(builder))
|
||||||
|
{
|
||||||
|
var client = server.CreateClient();
|
||||||
|
var initialResponse = await client.GetAsync("?parama=valuea¶mb=valueb");
|
||||||
|
var subsequentResponse = await client.GetAsync("?ParamA=valuea&ParamB=valueb");
|
||||||
|
|
||||||
|
await AssertResponseCachedAsync(initialResponse, subsequentResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async void ServesCachedContent_IfVaryByParamsExplicit_Matches_OrderInsensitive()
|
||||||
|
{
|
||||||
|
var builder = CreateBuilderWithResponseCaching(async (context) =>
|
||||||
|
{
|
||||||
|
var uniqueId = Guid.NewGuid().ToString();
|
||||||
|
var headers = context.Response.GetTypedHeaders();
|
||||||
|
headers.CacheControl = new CacheControlHeaderValue()
|
||||||
|
{
|
||||||
|
Public = true,
|
||||||
|
MaxAge = TimeSpan.FromSeconds(10)
|
||||||
|
};
|
||||||
|
headers.Date = DateTimeOffset.UtcNow;
|
||||||
|
headers.Headers["X-Value"] = uniqueId;
|
||||||
|
context.GetResponseCachingFeature().VaryByParams = new[] { "ParamB", "ParamA" };
|
||||||
|
await context.Response.WriteAsync(uniqueId);
|
||||||
|
});
|
||||||
|
|
||||||
|
using (var server = new TestServer(builder))
|
||||||
|
{
|
||||||
|
var client = server.CreateClient();
|
||||||
|
var initialResponse = await client.GetAsync("?ParamA=ValueA&ParamB=ValueB");
|
||||||
|
var subsequentResponse = await client.GetAsync("?ParamB=ValueB&ParamA=ValueA");
|
||||||
|
|
||||||
|
await AssertResponseCachedAsync(initialResponse, subsequentResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async void ServesCachedContent_IfVaryByParamsStar_Matches_OrderInsensitive()
|
||||||
|
{
|
||||||
|
var builder = CreateBuilderWithResponseCaching(async (context) =>
|
||||||
|
{
|
||||||
|
var uniqueId = Guid.NewGuid().ToString();
|
||||||
|
var headers = context.Response.GetTypedHeaders();
|
||||||
|
headers.CacheControl = new CacheControlHeaderValue()
|
||||||
|
{
|
||||||
|
Public = true,
|
||||||
|
MaxAge = TimeSpan.FromSeconds(10)
|
||||||
|
};
|
||||||
|
headers.Date = DateTimeOffset.UtcNow;
|
||||||
|
headers.Headers["X-Value"] = uniqueId;
|
||||||
|
context.GetResponseCachingFeature().VaryByParams = new[] { "*" };
|
||||||
|
await context.Response.WriteAsync(uniqueId);
|
||||||
|
});
|
||||||
|
|
||||||
|
using (var server = new TestServer(builder))
|
||||||
|
{
|
||||||
|
var client = server.CreateClient();
|
||||||
|
var initialResponse = await client.GetAsync("?ParamA=ValueA&ParamB=ValueB");
|
||||||
|
var subsequentResponse = await client.GetAsync("?ParamB=ValueB&ParamA=ValueA");
|
||||||
|
|
||||||
|
await AssertResponseCachedAsync(initialResponse, subsequentResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async void ServesFreshContent_IfVaryByParams_Mismatches()
|
||||||
|
{
|
||||||
|
var builder = CreateBuilderWithResponseCaching(async (context) =>
|
||||||
|
{
|
||||||
|
var uniqueId = Guid.NewGuid().ToString();
|
||||||
|
var headers = context.Response.GetTypedHeaders();
|
||||||
|
headers.CacheControl = new CacheControlHeaderValue()
|
||||||
|
{
|
||||||
|
Public = true,
|
||||||
|
MaxAge = TimeSpan.FromSeconds(10)
|
||||||
|
};
|
||||||
|
headers.Date = DateTimeOffset.UtcNow;
|
||||||
|
headers.Headers["X-Value"] = uniqueId;
|
||||||
|
context.GetResponseCachingFeature().VaryByParams = "param";
|
||||||
|
await context.Response.WriteAsync(uniqueId);
|
||||||
|
});
|
||||||
|
|
||||||
|
using (var server = new TestServer(builder))
|
||||||
|
{
|
||||||
|
var client = server.CreateClient();
|
||||||
|
var initialResponse = await client.GetAsync("?param=value");
|
||||||
|
var subsequentResponse = await client.GetAsync("?param=value2");
|
||||||
|
|
||||||
|
await AssertResponseNotCachedAsync(initialResponse, subsequentResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async void ServesFreshContent_IfVaryByParamsExplicit_Mismatch_ParamValueCaseSensitive()
|
||||||
|
{
|
||||||
|
var builder = CreateBuilderWithResponseCaching(async (context) =>
|
||||||
|
{
|
||||||
|
var uniqueId = Guid.NewGuid().ToString();
|
||||||
|
var headers = context.Response.GetTypedHeaders();
|
||||||
|
headers.CacheControl = new CacheControlHeaderValue()
|
||||||
|
{
|
||||||
|
Public = true,
|
||||||
|
MaxAge = TimeSpan.FromSeconds(10)
|
||||||
|
};
|
||||||
|
headers.Date = DateTimeOffset.UtcNow;
|
||||||
|
headers.Headers["X-Value"] = uniqueId;
|
||||||
|
context.GetResponseCachingFeature().VaryByParams = new[] { "ParamA", "ParamB" };
|
||||||
|
await context.Response.WriteAsync(uniqueId);
|
||||||
|
});
|
||||||
|
|
||||||
|
using (var server = new TestServer(builder))
|
||||||
|
{
|
||||||
|
var client = server.CreateClient();
|
||||||
|
var initialResponse = await client.GetAsync("?parama=valuea¶mb=valueb");
|
||||||
|
var subsequentResponse = await client.GetAsync("?parama=ValueA¶mb=ValueB");
|
||||||
|
|
||||||
|
await AssertResponseNotCachedAsync(initialResponse, subsequentResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async void ServesFreshContent_IfVaryByParamsStar_Mismatch_ParamValueCaseSensitive()
|
||||||
|
{
|
||||||
|
var builder = CreateBuilderWithResponseCaching(async (context) =>
|
||||||
|
{
|
||||||
|
var uniqueId = Guid.NewGuid().ToString();
|
||||||
|
var headers = context.Response.GetTypedHeaders();
|
||||||
|
headers.CacheControl = new CacheControlHeaderValue()
|
||||||
|
{
|
||||||
|
Public = true,
|
||||||
|
MaxAge = TimeSpan.FromSeconds(10)
|
||||||
|
};
|
||||||
|
headers.Date = DateTimeOffset.UtcNow;
|
||||||
|
headers.Headers["X-Value"] = uniqueId;
|
||||||
|
context.GetResponseCachingFeature().VaryByParams = new[] { "*" };
|
||||||
|
await context.Response.WriteAsync(uniqueId);
|
||||||
|
});
|
||||||
|
|
||||||
|
using (var server = new TestServer(builder))
|
||||||
|
{
|
||||||
|
var client = server.CreateClient();
|
||||||
|
var initialResponse = await client.GetAsync("?parama=valuea¶mb=valueb");
|
||||||
|
var subsequentResponse = await client.GetAsync("?parama=ValueA¶mb=ValueB");
|
||||||
|
|
||||||
|
await AssertResponseNotCachedAsync(initialResponse, subsequentResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async void ServesFreshContent_IfRequestRequirements_NotMet()
|
||||||
{
|
{
|
||||||
var builder = CreateBuilderWithResponseCaching(async (context) =>
|
var builder = CreateBuilderWithResponseCaching(async (context) =>
|
||||||
{
|
{
|
||||||
|
|
@ -147,50 +400,12 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
||||||
};
|
};
|
||||||
var subsequentResponse = await client.GetAsync("");
|
var subsequentResponse = await client.GetAsync("");
|
||||||
|
|
||||||
initialResponse.EnsureSuccessStatusCode();
|
await AssertResponseNotCachedAsync(initialResponse, subsequentResponse);
|
||||||
subsequentResponse.EnsureSuccessStatusCode();
|
|
||||||
|
|
||||||
Assert.False(subsequentResponse.Headers.Contains(HeaderNames.Age));
|
|
||||||
Assert.NotEqual(await initialResponse.Content.ReadAsStringAsync(), await subsequentResponse.Content.ReadAsStringAsync());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async void ServesFreshContentIfVaryByMismatches()
|
public async void Serves504_IfOnlyIfCachedHeader_IsSpecified()
|
||||||
{
|
|
||||||
var builder = CreateBuilderWithResponseCaching(async (context) =>
|
|
||||||
{
|
|
||||||
var uniqueId = Guid.NewGuid().ToString();
|
|
||||||
var headers = context.Response.GetTypedHeaders();
|
|
||||||
headers.CacheControl = new CacheControlHeaderValue()
|
|
||||||
{
|
|
||||||
Public = true,
|
|
||||||
MaxAge = TimeSpan.FromSeconds(10)
|
|
||||||
};
|
|
||||||
headers.Date = DateTimeOffset.UtcNow;
|
|
||||||
headers.Headers["X-Value"] = uniqueId;
|
|
||||||
context.Response.Headers[HeaderNames.Vary] = HeaderNames.From;
|
|
||||||
await context.Response.WriteAsync(uniqueId);
|
|
||||||
});
|
|
||||||
|
|
||||||
using (var server = new TestServer(builder))
|
|
||||||
{
|
|
||||||
var client = server.CreateClient();
|
|
||||||
client.DefaultRequestHeaders.From = "user@example.com";
|
|
||||||
var initialResponse = await client.GetAsync("");
|
|
||||||
client.DefaultRequestHeaders.From = "user2@example.com";
|
|
||||||
var subsequentResponse = await client.GetAsync("");
|
|
||||||
|
|
||||||
initialResponse.EnsureSuccessStatusCode();
|
|
||||||
subsequentResponse.EnsureSuccessStatusCode();
|
|
||||||
|
|
||||||
Assert.False(subsequentResponse.Headers.Contains(HeaderNames.Age));
|
|
||||||
Assert.NotEqual(await initialResponse.Content.ReadAsStringAsync(), await subsequentResponse.Content.ReadAsStringAsync());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async void Serves504IfOnlyIfCachedHeaderIsSpecified()
|
|
||||||
{
|
{
|
||||||
var builder = CreateBuilderWithResponseCaching(async (context) =>
|
var builder = CreateBuilderWithResponseCaching(async (context) =>
|
||||||
{
|
{
|
||||||
|
|
@ -222,7 +437,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async void ServesCachedContentWithoutSetCookie()
|
public async void ServesCachedContent_WithoutSetCookie()
|
||||||
{
|
{
|
||||||
var builder = CreateBuilderWithResponseCaching(async (context) =>
|
var builder = CreateBuilderWithResponseCaching(async (context) =>
|
||||||
{
|
{
|
||||||
|
|
@ -263,7 +478,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async void ServesCachedContentIfIHttpSendFileFeatureNotUsed()
|
public async void ServesCachedContent_IfIHttpSendFileFeature_NotUsed()
|
||||||
{
|
{
|
||||||
var builder = CreateBuilderWithResponseCaching(
|
var builder = CreateBuilderWithResponseCaching(
|
||||||
app =>
|
app =>
|
||||||
|
|
@ -294,20 +509,12 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
||||||
var initialResponse = await client.GetAsync("");
|
var initialResponse = await client.GetAsync("");
|
||||||
var subsequentResponse = await client.GetAsync("");
|
var subsequentResponse = await client.GetAsync("");
|
||||||
|
|
||||||
initialResponse.EnsureSuccessStatusCode();
|
await AssertResponseCachedAsync(initialResponse, subsequentResponse);
|
||||||
subsequentResponse.EnsureSuccessStatusCode();
|
|
||||||
|
|
||||||
foreach (var header in initialResponse.Headers)
|
|
||||||
{
|
|
||||||
Assert.Equal(initialResponse.Headers.GetValues(header.Key), subsequentResponse.Headers.GetValues(header.Key));
|
|
||||||
}
|
|
||||||
Assert.True(subsequentResponse.Headers.Contains(HeaderNames.Age));
|
|
||||||
Assert.Equal(await initialResponse.Content.ReadAsStringAsync(), await subsequentResponse.Content.ReadAsStringAsync());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async void ServesFreshContentIfIHttpSendFileFeatureUsed()
|
public async void ServesFreshContent_IfIHttpSendFileFeature_Used()
|
||||||
{
|
{
|
||||||
var builder = CreateBuilderWithResponseCaching(
|
var builder = CreateBuilderWithResponseCaching(
|
||||||
app =>
|
app =>
|
||||||
|
|
@ -339,16 +546,12 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
||||||
var initialResponse = await client.GetAsync("");
|
var initialResponse = await client.GetAsync("");
|
||||||
var subsequentResponse = await client.GetAsync("");
|
var subsequentResponse = await client.GetAsync("");
|
||||||
|
|
||||||
initialResponse.EnsureSuccessStatusCode();
|
await AssertResponseNotCachedAsync(initialResponse, subsequentResponse);
|
||||||
subsequentResponse.EnsureSuccessStatusCode();
|
|
||||||
|
|
||||||
Assert.False(subsequentResponse.Headers.Contains(HeaderNames.Age));
|
|
||||||
Assert.NotEqual(await initialResponse.Content.ReadAsStringAsync(), await subsequentResponse.Content.ReadAsStringAsync());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async void ServesCachedContentIfSubsequentRequestContainsNoStore()
|
public async void ServesCachedContent_IfSubsequentRequest_ContainsNoStore()
|
||||||
{
|
{
|
||||||
var builder = CreateBuilderWithResponseCaching(
|
var builder = CreateBuilderWithResponseCaching(
|
||||||
async (context) =>
|
async (context) =>
|
||||||
|
|
@ -375,20 +578,12 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
||||||
};
|
};
|
||||||
var subsequentResponse = await client.GetAsync("");
|
var subsequentResponse = await client.GetAsync("");
|
||||||
|
|
||||||
initialResponse.EnsureSuccessStatusCode();
|
await AssertResponseCachedAsync(initialResponse, subsequentResponse);
|
||||||
subsequentResponse.EnsureSuccessStatusCode();
|
|
||||||
|
|
||||||
foreach (var header in initialResponse.Headers)
|
|
||||||
{
|
|
||||||
Assert.Equal(initialResponse.Headers.GetValues(header.Key), subsequentResponse.Headers.GetValues(header.Key));
|
|
||||||
}
|
|
||||||
Assert.True(subsequentResponse.Headers.Contains(HeaderNames.Age));
|
|
||||||
Assert.Equal(await initialResponse.Content.ReadAsStringAsync(), await subsequentResponse.Content.ReadAsStringAsync());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async void ServesFreshContentIfInitialRequestContainsNoStore()
|
public async void ServesFreshContent_IfInitialRequestContains_NoStore()
|
||||||
{
|
{
|
||||||
var builder = CreateBuilderWithResponseCaching(
|
var builder = CreateBuilderWithResponseCaching(
|
||||||
async (context) =>
|
async (context) =>
|
||||||
|
|
@ -415,14 +610,32 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
||||||
var initialResponse = await client.GetAsync("");
|
var initialResponse = await client.GetAsync("");
|
||||||
var subsequentResponse = await client.GetAsync("");
|
var subsequentResponse = await client.GetAsync("");
|
||||||
|
|
||||||
initialResponse.EnsureSuccessStatusCode();
|
await AssertResponseNotCachedAsync(initialResponse, subsequentResponse);
|
||||||
subsequentResponse.EnsureSuccessStatusCode();
|
|
||||||
|
|
||||||
Assert.False(subsequentResponse.Headers.Contains(HeaderNames.Age));
|
|
||||||
Assert.NotEqual(await initialResponse.Content.ReadAsStringAsync(), await subsequentResponse.Content.ReadAsStringAsync());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task AssertResponseCachedAsync(HttpResponseMessage initialResponse, HttpResponseMessage subsequentResponse)
|
||||||
|
{
|
||||||
|
initialResponse.EnsureSuccessStatusCode();
|
||||||
|
subsequentResponse.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
foreach (var header in initialResponse.Headers)
|
||||||
|
{
|
||||||
|
Assert.Equal(initialResponse.Headers.GetValues(header.Key), subsequentResponse.Headers.GetValues(header.Key));
|
||||||
|
}
|
||||||
|
Assert.True(subsequentResponse.Headers.Contains(HeaderNames.Age));
|
||||||
|
Assert.Equal(await initialResponse.Content.ReadAsStringAsync(), await subsequentResponse.Content.ReadAsStringAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task AssertResponseNotCachedAsync(HttpResponseMessage initialResponse, HttpResponseMessage subsequentResponse)
|
||||||
|
{
|
||||||
|
initialResponse.EnsureSuccessStatusCode();
|
||||||
|
subsequentResponse.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
Assert.False(subsequentResponse.Headers.Contains(HeaderNames.Age));
|
||||||
|
Assert.NotEqual(await initialResponse.Content.ReadAsStringAsync(), await subsequentResponse.Content.ReadAsStringAsync());
|
||||||
|
}
|
||||||
|
|
||||||
private static IWebHostBuilder CreateBuilderWithResponseCaching(RequestDelegate requestDelegate) =>
|
private static IWebHostBuilder CreateBuilderWithResponseCaching(RequestDelegate requestDelegate) =>
|
||||||
CreateBuilderWithResponseCaching(app => { }, requestDelegate);
|
CreateBuilderWithResponseCaching(app => { }, requestDelegate);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue